aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2019-05-17 21:52:49 -0400
committerBob Zeleznik <zzzman@gmail.com>2019-05-17 21:52:49 -0400
commit618b4a42795b59cde47510b86b6e25dc03e15935 (patch)
treef10a9f093df478db15e94fbf8992a32fe8ba99d0
parent19fca408a19c5f7a759ff6c3bfefe27b96ec3563 (diff)
parent4e244951b7b18d7973360f423e8de80c42466228 (diff)
merged
-rw-r--r--.gitignore2
-rw-r--r--.vscode/launch.json20
-rw-r--r--.vscode/settings.json3
-rw-r--r--build/index.html6
-rw-r--r--deploy/assets/env.json15
-rw-r--r--deploy/index.html14
-rw-r--r--deploy/mobile/image.html15
-rw-r--r--deploy/mobile/ink.html13
-rw-r--r--package-lock.json2011
-rw-r--r--package.json311
-rw-r--r--solr/conf/lang/contractions_ca.txt8
-rw-r--r--solr/conf/lang/contractions_fr.txt15
-rw-r--r--solr/conf/lang/contractions_ga.txt5
-rw-r--r--solr/conf/lang/contractions_it.txt23
-rw-r--r--solr/conf/lang/hyphenations_ga.txt5
-rw-r--r--solr/conf/lang/stemdict_nl.txt6
-rw-r--r--solr/conf/lang/stoptags_ja.txt420
-rw-r--r--solr/conf/lang/stopwords_ar.txt125
-rw-r--r--solr/conf/lang/stopwords_bg.txt193
-rw-r--r--solr/conf/lang/stopwords_ca.txt220
-rw-r--r--solr/conf/lang/stopwords_cz.txt172
-rw-r--r--solr/conf/lang/stopwords_da.txt110
-rw-r--r--solr/conf/lang/stopwords_de.txt294
-rw-r--r--solr/conf/lang/stopwords_el.txt78
-rw-r--r--solr/conf/lang/stopwords_en.txt54
-rw-r--r--solr/conf/lang/stopwords_es.txt356
-rw-r--r--solr/conf/lang/stopwords_eu.txt99
-rw-r--r--solr/conf/lang/stopwords_fa.txt313
-rw-r--r--solr/conf/lang/stopwords_fi.txt97
-rw-r--r--solr/conf/lang/stopwords_fr.txt186
-rw-r--r--solr/conf/lang/stopwords_ga.txt110
-rw-r--r--solr/conf/lang/stopwords_gl.txt161
-rw-r--r--solr/conf/lang/stopwords_hi.txt235
-rw-r--r--solr/conf/lang/stopwords_hu.txt211
-rw-r--r--solr/conf/lang/stopwords_hy.txt46
-rw-r--r--solr/conf/lang/stopwords_id.txt359
-rw-r--r--solr/conf/lang/stopwords_it.txt303
-rw-r--r--solr/conf/lang/stopwords_ja.txt127
-rw-r--r--solr/conf/lang/stopwords_lv.txt172
-rw-r--r--solr/conf/lang/stopwords_nl.txt119
-rw-r--r--solr/conf/lang/stopwords_no.txt194
-rw-r--r--solr/conf/lang/stopwords_pt.txt253
-rw-r--r--solr/conf/lang/stopwords_ro.txt233
-rw-r--r--solr/conf/lang/stopwords_ru.txt243
-rw-r--r--solr/conf/lang/stopwords_sv.txt133
-rw-r--r--solr/conf/lang/stopwords_th.txt119
-rw-r--r--solr/conf/lang/stopwords_tr.txt212
-rw-r--r--solr/conf/lang/userdict_ja.txt29
-rw-r--r--solr/conf/params.json20
-rw-r--r--solr/conf/protwords.txt21
-rw-r--r--solr/conf/schema.xml61
-rw-r--r--solr/conf/schema.xml~23
-rw-r--r--solr/conf/solrconfig.xml1328
-rw-r--r--solr/conf/solrconfig.xml~1358
-rw-r--r--solr/conf/stopwords.txt14
-rw-r--r--solr/conf/synonyms.txt29
-rw-r--r--src/.DS_Storebin6148 -> 6148 bytes
-rw-r--r--src/Utils.ts99
-rw-r--r--src/client/DocServer.ts130
-rw-r--r--src/client/Server.ts124
-rw-r--r--src/client/SocketStub.ts95
-rw-r--r--src/client/documents/Documents.ts406
-rw-r--r--src/client/goldenLayout.d.ts3
-rw-r--r--src/client/goldenLayout.js5360
-rw-r--r--src/client/northstar/core/BaseObject.ts12
-rw-r--r--src/client/northstar/core/attribute/AttributeModel.ts111
-rw-r--r--src/client/northstar/core/attribute/AttributeTransformationModel.ts52
-rw-r--r--src/client/northstar/core/attribute/CalculatedAttributeModel.ts42
-rw-r--r--src/client/northstar/core/brusher/IBaseBrushable.ts13
-rw-r--r--src/client/northstar/core/brusher/IBaseBrusher.ts11
-rw-r--r--src/client/northstar/core/filter/FilterModel.ts83
-rw-r--r--src/client/northstar/core/filter/FilterOperand.ts5
-rw-r--r--src/client/northstar/core/filter/FilterType.ts6
-rw-r--r--src/client/northstar/core/filter/IBaseFilterConsumer.ts12
-rw-r--r--src/client/northstar/core/filter/IBaseFilterProvider.ts8
-rw-r--r--src/client/northstar/core/filter/ValueComparision.ts78
-rw-r--r--src/client/northstar/dash-fields/HistogramField.ts58
-rw-r--r--src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts240
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.scss40
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.tsx174
-rw-r--r--src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss42
-rw-r--r--src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx124
-rw-r--r--src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss13
-rw-r--r--src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx80
-rw-r--r--src/client/northstar/manager/Gateway.ts299
-rw-r--r--src/client/northstar/model/ModelExtensions.ts48
-rw-r--r--src/client/northstar/model/ModelHelpers.ts220
-rw-r--r--src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts52
-rw-r--r--src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts105
-rw-r--r--src/client/northstar/model/binRanges/NominalVisualBinRange.ts52
-rw-r--r--src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts90
-rw-r--r--src/client/northstar/model/binRanges/VisualBinRange.ts32
-rw-r--r--src/client/northstar/model/binRanges/VisualBinRangeHelper.ts70
-rw-r--r--src/client/northstar/model/idea/MetricTypeMapping.ts30
-rw-r--r--src/client/northstar/model/idea/idea.ts8557
-rw-r--r--src/client/northstar/operations/BaseOperation.ts162
-rw-r--r--src/client/northstar/operations/HistogramOperation.ts158
-rw-r--r--src/client/northstar/utils/ArrayUtil.ts90
-rw-r--r--src/client/northstar/utils/Extensions.ts29
-rw-r--r--src/client/northstar/utils/GeometryUtil.ts133
-rw-r--r--src/client/northstar/utils/IDisposable.ts3
-rw-r--r--src/client/northstar/utils/IEquatable.ts3
-rw-r--r--src/client/northstar/utils/KeyCodes.ts137
-rw-r--r--src/client/northstar/utils/LABColor.ts90
-rw-r--r--src/client/northstar/utils/MathUtil.ts249
-rw-r--r--src/client/northstar/utils/PartialClass.ts7
-rw-r--r--src/client/northstar/utils/SizeConverter.ts101
-rw-r--r--src/client/northstar/utils/StyleContants.ts95
-rw-r--r--src/client/northstar/utils/Utils.ts75
-rw-r--r--src/client/util/DocumentManager.ts128
-rw-r--r--src/client/util/DragManager.ts344
-rw-r--r--src/client/util/History.ts122
-rw-r--r--src/client/util/ProsemirrorKeymap.ts100
-rw-r--r--src/client/util/RichTextRules.ts43
-rw-r--r--src/client/util/RichTextSchema.tsx589
-rw-r--r--src/client/util/Scripting.ts141
-rw-r--r--src/client/util/ScrollBox.tsx4
-rw-r--r--src/client/util/SearchUtil.ts25
-rw-r--r--src/client/util/SelectionManager.ts63
-rw-r--r--src/client/util/SerializationHelper.ts130
-rw-r--r--src/client/util/TooltipLinkingMenu.tsx86
-rw-r--r--src/client/util/TooltipTextMenu.scss258
-rw-r--r--src/client/util/TooltipTextMenu.tsx575
-rw-r--r--src/client/util/Transform.ts56
-rw-r--r--src/client/util/TypedEvent.ts4
-rw-r--r--src/client/util/UndoManager.ts105
-rw-r--r--src/client/util/jsx-decl.d.ts1
-rw-r--r--src/client/util/type_decls.d12
-rw-r--r--src/client/views/.DS_Storebin6148 -> 6148 bytes
-rw-r--r--src/client/views/ContextMenu.scss33
-rw-r--r--src/client/views/ContextMenu.tsx47
-rw-r--r--src/client/views/ContextMenuItem.tsx7
-rw-r--r--src/client/views/DocComponent.tsx14
-rw-r--r--src/client/views/DocumentDecorations.scss205
-rw-r--r--src/client/views/DocumentDecorations.tsx565
-rw-r--r--src/client/views/EditableView.scss20
-rw-r--r--src/client/views/EditableView.tsx34
-rw-r--r--src/client/views/InkingCanvas.scss63
-rw-r--r--src/client/views/InkingCanvas.tsx241
-rw-r--r--src/client/views/InkingControl.scss135
-rw-r--r--src/client/views/InkingControl.tsx79
-rw-r--r--src/client/views/InkingStroke.scss3
-rw-r--r--src/client/views/InkingStroke.tsx34
-rw-r--r--src/client/views/Main.scss191
-rw-r--r--src/client/views/Main.tsx358
-rw-r--r--src/client/views/MainOverlayTextBox.scss20
-rw-r--r--src/client/views/MainOverlayTextBox.tsx99
-rw-r--r--src/client/views/PresentationView.scss79
-rw-r--r--src/client/views/PresentationView.tsx150
-rw-r--r--src/client/views/PreviewCursor.scss9
-rw-r--r--src/client/views/PreviewCursor.tsx64
-rw-r--r--src/client/views/SearchBox.scss102
-rw-r--r--src/client/views/SearchBox.tsx178
-rw-r--r--src/client/views/SearchItem.tsx73
-rw-r--r--src/client/views/TemplateMenu.tsx90
-rw-r--r--src/client/views/Templates.tsx92
-rw-r--r--src/client/views/_nodeModuleOverrides.scss23
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx176
-rw-r--r--src/client/views/collections/CollectionDockingView.scss23
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx360
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss75
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx394
-rw-r--r--src/client/views/collections/CollectionPDFView.scss49
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx102
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss200
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx454
-rw-r--r--src/client/views/collections/CollectionSubView.tsx231
-rw-r--r--src/client/views/collections/CollectionTreeView.scss107
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx308
-rw-r--r--src/client/views/collections/CollectionVideoView.scss42
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx89
-rw-r--r--src/client/views/collections/CollectionView.tsx146
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx152
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss8
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx66
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx58
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx130
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss24
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx87
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss107
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx404
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss26
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx367
-rw-r--r--src/client/views/globalCssVariables.scss35
-rw-r--r--src/client/views/globalCssVariables.scss.d.ts10
-rw-r--r--src/client/views/nodes/Annotation.tsx128
-rw-r--r--src/client/views/nodes/AudioBox.tsx43
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx304
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx111
-rw-r--r--src/client/views/nodes/DocumentView.scss54
-rw-r--r--src/client/views/nodes/DocumentView.tsx517
-rw-r--r--src/client/views/nodes/FieldTextBox.scss14
-rw-r--r--src/client/views/nodes/FieldView.tsx127
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss80
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx374
-rw-r--r--src/client/views/nodes/IconBox.scss22
-rw-r--r--src/client/views/nodes/IconBox.tsx84
-rw-r--r--src/client/views/nodes/ImageBox.scss47
-rw-r--r--src/client/views/nodes/ImageBox.tsx149
-rw-r--r--src/client/views/nodes/KeyValueBox.scss116
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx114
-rw-r--r--src/client/views/nodes/KeyValuePair.scss37
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx106
-rw-r--r--src/client/views/nodes/LinkBox.scss43
-rw-r--r--src/client/views/nodes/LinkBox.tsx85
-rw-r--r--src/client/views/nodes/LinkEditor.scss23
-rw-r--r--src/client/views/nodes/LinkEditor.tsx21
-rw-r--r--src/client/views/nodes/LinkMenu.scss1
-rw-r--r--src/client/views/nodes/LinkMenu.tsx36
-rw-r--r--src/client/views/nodes/PDFBox.scss24
-rw-r--r--src/client/views/nodes/PDFBox.tsx407
-rw-r--r--src/client/views/nodes/Sticky.tsx83
-rw-r--r--src/client/views/nodes/VideoBox.scss8
-rw-r--r--src/client/views/nodes/VideoBox.tsx176
-rw-r--r--src/client/views/nodes/WebBox.scss25
-rw-r--r--src/client/views/nodes/WebBox.tsx67
-rw-r--r--src/debug/Test.tsx97
-rw-r--r--src/debug/Viewer.tsx368
-rw-r--r--src/fields/AudioField.ts31
-rw-r--r--src/fields/BasicField.ts59
-rw-r--r--src/fields/Document.ts306
-rw-r--r--src/fields/DocumentReference.ts57
-rw-r--r--src/fields/Field.ts69
-rw-r--r--src/fields/FieldUpdatedArgs.ts27
-rw-r--r--src/fields/HtmlField.ts25
-rw-r--r--src/fields/ImageField.ts29
-rw-r--r--src/fields/InkField.ts53
-rw-r--r--src/fields/KVPField.ts30
-rw-r--r--src/fields/Key.ts50
-rw-r--r--src/fields/KeyStore.ts38
-rw-r--r--src/fields/ListField.ts122
-rw-r--r--src/fields/NumberField.ts25
-rw-r--r--src/fields/PDFField.ts36
-rw-r--r--src/fields/RichTextField.ts26
-rw-r--r--src/fields/ScriptField.ts101
-rw-r--r--src/fields/TextField.ts25
-rw-r--r--src/fields/VideoField.ts30
-rw-r--r--src/fields/WebField.ts30
-rw-r--r--src/mobile/ImageUpload.scss13
-rw-r--r--src/mobile/ImageUpload.tsx76
-rw-r--r--src/mobile/InkControls.tsx (renamed from src/fields/KVPField)0
-rw-r--r--src/new_fields/CursorField.ts55
-rw-r--r--src/new_fields/DateField.ts18
-rw-r--r--src/new_fields/Doc.ts257
-rw-r--r--src/new_fields/HtmlField.ts18
-rw-r--r--src/new_fields/IconField.ts18
-rw-r--r--src/new_fields/InkField.ts43
-rw-r--r--src/new_fields/List.ts289
-rw-r--r--src/new_fields/ObjectField.ts18
-rw-r--r--src/new_fields/Proxy.ts65
-rw-r--r--src/new_fields/RefField.ts18
-rw-r--r--src/new_fields/RichTextField.ts18
-rw-r--r--src/new_fields/Schema.ts82
-rw-r--r--src/new_fields/Types.ts88
-rw-r--r--src/new_fields/URLField.ts34
-rw-r--r--src/new_fields/util.ts111
-rw-r--r--src/server/Client.ts12
-rw-r--r--src/server/Message.ts127
-rw-r--r--src/server/RouteStore.ts30
-rw-r--r--src/server/Search.ts49
-rw-r--r--src/server/ServerUtil.ts71
-rw-r--r--src/server/authentication/config/passport.ts13
-rw-r--r--src/server/authentication/controllers/user.ts107
-rw-r--r--src/server/authentication/controllers/user_controller.ts266
-rw-r--r--src/server/authentication/models/current_user_utils.ts137
-rw-r--r--src/server/authentication/models/user_model.ts (renamed from src/server/authentication/models/User.ts)35
-rw-r--r--src/server/database.ts123
-rw-r--r--src/server/index.ts426
-rw-r--r--src/server/public/files/.gitignore1
-rw-r--r--src/typings/index.d.ts604
-rw-r--r--test/test.ts168
-rw-r--r--tsconfig.json40
-rw-r--r--tslint.json57
-rw-r--r--views/forgot.pug22
-rw-r--r--views/layout.pug5
-rw-r--r--views/login.pug28
-rw-r--r--views/reset.pug22
-rw-r--r--views/resources/dashlogo.pngbin0 -> 7169 bytes
-rw-r--r--views/signup.pug44
-rw-r--r--views/stylesheets/authentication.css142
-rw-r--r--webpack.config.js160
283 files changed, 41928 insertions, 6700 deletions
diff --git a/.gitignore b/.gitignore
index 7b9483f69..a499c39a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
node_modules
+package-lock.json
dist/
.DS_Store
-src/server/public/files/*
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 074f9ddf0..452acc823 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,10 +10,28 @@
"name": "Launch Chrome against localhost",
"sourceMaps": true,
"breakOnLoad": true,
- "url": "http://localhost:1050",
+ "url": "http://localhost:1050/login",
"webRoot": "${workspaceFolder}",
},
{
+ "type": "chrome",
+ "request": "launch",
+ "name": "Launch Chrome against Dash server",
+ "sourceMaps": true,
+ "breakOnLoad": true,
+ "url": "http://dash-web.eastus2.cloudapp.azure.com:1050/login",
+ "webRoot": "${workspaceFolder}",
+ },
+ {
+ "type": "node",
+ "request": "attach",
+ "name": "Typescript Server",
+ "protocol": "inspector",
+ "port": 9229,
+ "localRoot": "${workspaceFolder}",
+ "remoteRoot": "."
+ },
+ {
"type": "node",
"request": "launch",
"name": "Mocha Tests",
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 623dae755..fc315ffaf 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -7,6 +7,7 @@
"**/.DS_Store": true,
},
"editor.formatOnSave": true,
+ "editor.detectIndentation": false,
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
- "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true,
+ "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true
} \ No newline at end of file
diff --git a/build/index.html b/build/index.html
index fda212af4..a738d1092 100644
--- a/build/index.html
+++ b/build/index.html
@@ -1,12 +1,12 @@
<html>
<head>
- <title>Dash Web</title>
+ <title>Dash Web Build</title>
</head>
<body>
- <div id="root"></div>
- <script src="./bundle.js"></script>
+ <div id="root"></div>
+ <script src="./bundle.js"></script>
</body>
</html> \ No newline at end of file
diff --git a/deploy/assets/env.json b/deploy/assets/env.json
new file mode 100644
index 000000000..80963ea0d
--- /dev/null
+++ b/deploy/assets/env.json
@@ -0,0 +1,15 @@
+{
+ "IS_DARPA": false,
+ "IS_IGT": false,
+ "IS_MENU_FIXED": true,
+ "SERVER_URL": "http://localhost:1234",
+ "SERVER_API_PATH": "api",
+ "SAMPLE_SIZE": 100000,
+ "X_BINS": 15,
+ "Y_BINS": 15,
+ "SPLASH_TIME_IN_MS": 0,
+ "SHOW_FPS_COUNTER": true,
+ "SHOW_SHUTDOWN_BUTTON": false,
+ "DEGREE_OF_PARALLISM": 1,
+ "SHOW_WARNINGS": false
+} \ No newline at end of file
diff --git a/deploy/index.html b/deploy/index.html
index e0892662a..532b995f8 100644
--- a/deploy/index.html
+++ b/deploy/index.html
@@ -1,14 +1,16 @@
-<html>
+<html style="overflow: hidden;">
<head>
- <title>Dash Web</title>
- <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
- <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
+ <title>Dash Web</title>
+ <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
+ integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script>
</head>
<body>
- <div id="root"></div>
- <script src="./bundle.js"></script>
+ <div id="root"></div>
+ <script src="/bundle.js"></script>
</body>
</html> \ No newline at end of file
diff --git a/deploy/mobile/image.html b/deploy/mobile/image.html
new file mode 100644
index 000000000..6424d2a60
--- /dev/null
+++ b/deploy/mobile/image.html
@@ -0,0 +1,15 @@
+<html>
+
+<head>
+ <title>Test view</title>
+ <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
+</head>
+
+<body>
+ <div id="root">
+ <p>Capture Image: <input type="file" accept="image/*" id="capture">
+ </div>
+ <script src="../imageUpload.js"></script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/deploy/mobile/ink.html b/deploy/mobile/ink.html
new file mode 100644
index 000000000..938d85794
--- /dev/null
+++ b/deploy/mobile/ink.html
@@ -0,0 +1,13 @@
+<html>
+
+<head>
+ <title>Test view</title>
+ <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet">
+</head>
+
+<body>
+ <div id="root"></div>
+ <script src="../inkControls.js"></script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index ff4f06d03..6d4719d29 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,15 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "@babel/code-frame": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
+ "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.0.0"
+ }
+ },
"@babel/helper-module-imports": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
@@ -12,6 +21,48 @@
"@babel/types": "^7.0.0"
}
},
+ "@babel/highlight": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
+ "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.0",
+ "esutils": "^2.0.2",
+ "js-tokens": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
"@babel/runtime": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz",
@@ -100,12 +151,6 @@
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.15.tgz",
"integrity": "sha512-ATBRyKJw1d2ko+0DWN9+BXau0EK3I/Q6pPzPv3LhJD7r052YFAkAdfb1Bd7ZqhBsJrdse/S7jKxWUOZ61qBD4g=="
},
- "@fortawesome/fontawesome-free": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.7.2.tgz",
- "integrity": "sha512-Ha4HshKdCVKgu4TVCtG8XyPPYdzTzNW4/fvPnn+LT7AosRABryhlRv4cc4+o84dgpvVJN9reN7jo/c+nYujFug==",
- "dev": true
- },
"@fortawesome/fontawesome-free-solid": {
"version": "5.0.13",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.13.tgz",
@@ -158,11 +203,10 @@
}
},
"@hig/theme-context": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@hig/theme-context/-/theme-context-2.1.2.tgz",
- "integrity": "sha512-JTdYJgJpbPX0KPrjBkeKUFq8fLq7PFmMaBQaT71z6vs/ARqdeYC1ZNpksH2HCzDNU7mjGgFineA9He3eR3sgpg==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@hig/theme-context/-/theme-context-2.1.3.tgz",
+ "integrity": "sha512-c0Ju+Z8C532ZZtjwOLzN+XeO+pL3kqUawu6ZG3J084MH5RM9W8JCKyMf4D9Qr38jFWoiX6u8yiSxxjV/mz9Sqw==",
"requires": {
- "@hig/theme-data": "^2.3.2",
"create-react-context": "^0.2.3",
"prop-types": "^15.6.1"
}
@@ -180,11 +224,67 @@
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw=="
},
+ "@log4js-node/log4js-api": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@log4js-node/log4js-api/-/log4js-api-1.0.2.tgz",
+ "integrity": "sha512-6SJfx949YEWooh/CUPpJ+F491y4BYJmknz4hUN1+RHvKoUEynKbRmhnwbk/VLmh4OthLLDNCyWXfbh4DG1cTXA=="
+ },
+ "@react-bootstrap/react-popper": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@react-bootstrap/react-popper/-/react-popper-1.2.1.tgz",
+ "integrity": "sha512-4l3q7LcZEhrSkI4d3Ie3g4CdrXqqTexXX4PFT45CB0z5z2JUbaxgRwKNq7r5j2bLdVpZm+uvUGqxJw8d9vgbJQ==",
+ "requires": {
+ "babel-runtime": "6.x.x",
+ "create-react-context": "^0.2.1",
+ "popper.js": "^1.14.4",
+ "prop-types": "^15.6.1",
+ "typed-styles": "^0.0.5",
+ "warning": "^3.0.0"
+ }
+ },
+ "@restart/context": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz",
+ "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q=="
+ },
+ "@restart/hooks": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.2.13.tgz",
+ "integrity": "sha512-riuYub4Xx/Pk+YUPRHZPwJd2+rAJSsRd4Pqkqvhzu+AbkDdNi4y+kYEQicnAofbfTA5apRPm3cK9sWc6Gj5DNg=="
+ },
+ "@trendmicro/react-buttons": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@trendmicro/react-buttons/-/react-buttons-1.3.1.tgz",
+ "integrity": "sha512-9zvt/fdkqCb9kxUdZnvTZKmbmykM2wDQ3VEJFtztGcKAkm4Wkq4oZOQLJXKfUQ1vX3w+YDJob18LkNOzaHI1UQ==",
+ "requires": {
+ "classnames": "^2.2.5",
+ "prop-types": "^15.5.8"
+ }
+ },
+ "@trendmicro/react-dropdown": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@trendmicro/react-dropdown/-/react-dropdown-1.3.0.tgz",
+ "integrity": "sha512-KwL0ksEZPay7qNsiGcPQ3aGmyfJCcUuIjiD9HZs6L66ScwSRoFkDlAjMTlRVLFcYVNhpuyUH4pPiFlKQQzDHGQ==",
+ "requires": {
+ "@trendmicro/react-buttons": "^1.3.0",
+ "chained-function": "^0.5.0",
+ "classnames": "^2.2.5",
+ "dom-helpers": "^3.3.1",
+ "prop-types": "^15.6.0",
+ "uncontrollable": "^5.0.0",
+ "warning": "^3.0.0"
+ }
+ },
"@types/anymatch": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA=="
},
+ "@types/async": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@types/async/-/async-2.4.2.tgz",
+ "integrity": "sha512-bWBbC7VG2jdjbgZMX0qpds8U/3h3anfIqE81L8jmVrgFZw/urEDnBA78ymGGKTTK6ciBXmmJ/xlok+Re41S8ww=="
+ },
"@types/babel-types": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.6.tgz",
@@ -252,6 +352,28 @@
"@types/express": "*"
}
},
+ "@types/cookie-parser": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.1.tgz",
+ "integrity": "sha512-iJY6B3ZGufLiDf2OCAgiAAQuj1sMKC/wz/7XCEjZ+/MDuultfFJuSwrBKcLSmJ5iYApLzCCYBYJZs0Ws8GPmwA==",
+ "requires": {
+ "@types/express": "*"
+ }
+ },
+ "@types/cookie-session": {
+ "version": "2.0.37",
+ "resolved": "https://registry.npmjs.org/@types/cookie-session/-/cookie-session-2.0.37.tgz",
+ "integrity": "sha512-h8uZLDGyfAgER6kHbHlYWm1g/P/7zCBMOW6yT5/fQydVJxByJD4tohSvHBzJrGoLVmQJefQdfwuNkKb23cq29Q==",
+ "requires": {
+ "@types/express": "*",
+ "@types/keygrip": "*"
+ }
+ },
+ "@types/d3-format": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.3.1.tgz",
+ "integrity": "sha512-KAWvReOKMDreaAwOjdfQMm0HjcUMlQG47GwqdVKgmm20vTd2pucj0a70c3gUSHrnsmo6H2AMrkBsZU2UhJLq8A=="
+ },
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@@ -319,6 +441,17 @@
"@types/node": "*"
}
},
+ "@types/glob": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+ "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
+ "dev": true,
+ "requires": {
+ "@types/events": "*",
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
"@types/jquery": {
"version": "3.3.29",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.29.tgz",
@@ -335,6 +468,11 @@
"@types/node": "*"
}
},
+ "@types/keygrip": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.1.tgz",
+ "integrity": "sha1-/1QEYtL7TQqIRBzq8n0oewHD2Hg="
+ },
"@types/lodash": {
"version": "4.14.122",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.122.tgz",
@@ -360,6 +498,20 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
},
+ "@types/minimatch": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
+ "dev": true
+ },
+ "@types/mobile-detect": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/@types/mobile-detect/-/mobile-detect-1.3.4.tgz",
+ "integrity": "sha512-MGBTvT5c7aH8eX6szFYP3dWPryNLt5iGlo31XNaJtt8o6jsg6tjn99eEMq9l8T6cPZymsr+J4Jth8+/G/04ZDw==",
+ "requires": {
+ "mobile-detect": "*"
+ }
+ },
"@types/mocha": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz",
@@ -389,6 +541,14 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.30.tgz",
"integrity": "sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q=="
},
+ "@types/nodemailer": {
+ "version": "4.6.8",
+ "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-4.6.8.tgz",
+ "integrity": "sha512-IX1P3bxDP1VIdZf6/kIWYNmSejkYm9MOyMEtoDFi4DVzKjJ3kY4GhOcOAKs6lZRjqVVmF9UjPOZXuQczlpZThw==",
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/orderedmap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/orderedmap/-/orderedmap-1.0.0.tgz",
@@ -445,6 +605,15 @@
"@types/prosemirror-state": "*"
}
},
+ "@types/prosemirror-inputrules": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@types/prosemirror-inputrules/-/prosemirror-inputrules-1.0.2.tgz",
+ "integrity": "sha512-bKFneQUPnkZmzCJ1uoitpKH6PFW0hc4q55NsC7mFUCvX0eZl0GRKxyfV47jkJbsbyUQoO/QFv0WwLDz2bo15sA==",
+ "requires": {
+ "@types/prosemirror-model": "*",
+ "@types/prosemirror-state": "*"
+ }
+ },
"@types/prosemirror-keymap": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/prosemirror-keymap/-/prosemirror-keymap-1.0.1.tgz",
@@ -454,6 +623,16 @@
"@types/prosemirror-view": "*"
}
},
+ "@types/prosemirror-menu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@types/prosemirror-menu/-/prosemirror-menu-1.0.1.tgz",
+ "integrity": "sha512-wVGc6G7uYRvjIuVwV0zKSLwntFH1wanFwM1fDkq2YcUrLhuj4zZ1i7IPe+yqSoPm7JfmjiDEgHXTpafmwLKJrA==",
+ "requires": {
+ "@types/prosemirror-model": "*",
+ "@types/prosemirror-state": "*",
+ "@types/prosemirror-view": "*"
+ }
+ },
"@types/prosemirror-model": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@types/prosemirror-model/-/prosemirror-model-1.7.0.tgz",
@@ -571,6 +750,15 @@
"@types/tough-cookie": "*"
}
},
+ "@types/request-promise": {
+ "version": "4.1.43",
+ "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.43.tgz",
+ "integrity": "sha512-SEJaRP9jfxS7IbgjmcRfNOEbT1wcgVEgSYX0iyABO+0vkLdtB4xjvvYF6HBvYTCbSBF+KKgdtN2mfB8ua8JlXA==",
+ "requires": {
+ "@types/bluebird": "*",
+ "@types/request": "*"
+ }
+ },
"@types/serve-static": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz",
@@ -598,6 +786,18 @@
"resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.32.tgz",
"integrity": "sha512-Vs55Kq8F+OWvy1RLA31rT+cAyemzgm0EWNeax6BWF8H7QiiOYMJIdcwSDdm5LVgfEkoepsWkS+40+WNb7BUMbg=="
},
+ "@types/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=",
+ "dev": true
+ },
+ "@types/strip-json-comments": {
+ "version": "0.0.30",
+ "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz",
+ "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
+ "dev": true
+ },
"@types/tapable": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz",
@@ -1021,6 +1221,11 @@
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+ },
"anymatch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
@@ -1072,6 +1277,12 @@
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E="
},
+ "array-flatten": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
+ "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
+ "dev": true
+ },
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@@ -1120,7 +1331,6 @@
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
- "dev": true,
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
@@ -1144,7 +1354,7 @@
},
"util": {
"version": "0.10.3",
- "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
"dev": true,
"requires": {
@@ -1175,11 +1385,11 @@
"integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk="
},
"async": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
- "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
+ "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==",
"requires": {
- "lodash": "^4.17.10"
+ "lodash": "^4.17.11"
}
},
"async-each": {
@@ -1264,6 +1474,25 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+ "dev": true,
+ "requires": {
+ "chalk": "^1.1.3",
+ "esutils": "^2.0.2",
+ "js-tokens": "^3.0.2"
+ },
+ "dependencies": {
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+ "dev": true
+ }
+ }
+ },
"babel-plugin-emotion": {
"version": "10.0.7",
"resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.7.tgz",
@@ -1485,8 +1714,7 @@
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
- "dev": true
+ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
},
"body-parser": {
"version": "1.18.3",
@@ -1527,16 +1755,13 @@
"dns-txt": "^2.0.2",
"multicast-dns": "^6.0.1",
"multicast-dns-service-types": "^1.1.0"
- },
- "dependencies": {
- "array-flatten": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
- "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
- "dev": true
- }
}
},
+ "bootstrap": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz",
+ "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
+ },
"boxen": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
@@ -1650,8 +1875,7 @@
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
- "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
- "dev": true
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
},
"browser-stdout": {
"version": "1.3.1",
@@ -1661,9 +1885,8 @@
},
"browserify-aes": {
"version": "1.2.0",
- "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
- "dev": true,
"requires": {
"buffer-xor": "^1.0.3",
"cipher-base": "^1.0.0",
@@ -1677,7 +1900,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
"integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
- "dev": true,
"requires": {
"browserify-aes": "^1.0.4",
"browserify-des": "^1.0.0",
@@ -1688,7 +1910,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
"integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
- "dev": true,
"requires": {
"cipher-base": "^1.0.1",
"des.js": "^1.0.0",
@@ -1698,9 +1919,8 @@
},
"browserify-rsa": {
"version": "4.0.1",
- "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
- "dev": true,
"requires": {
"bn.js": "^4.1.0",
"randombytes": "^2.0.1"
@@ -1710,7 +1930,6 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
"integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
- "dev": true,
"requires": {
"bn.js": "^4.1.1",
"browserify-rsa": "^4.0.0",
@@ -1737,7 +1956,7 @@
},
"buffer": {
"version": "4.9.1",
- "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
"dev": true,
"requires": {
@@ -1771,7 +1990,12 @@
"buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
- "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true
},
"builtin-status-codes": {
@@ -1856,20 +2080,18 @@
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
"integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA="
},
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
+ },
"camelcase-keys": {
"version": "2.1.0",
- "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
"integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
"requires": {
"camelcase": "^2.0.0",
"map-obj": "^1.0.0"
- },
- "dependencies": {
- "camelcase": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
- "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
- }
}
},
"capture-stack-trace": {
@@ -1905,6 +2127,23 @@
"type-detect": "^4.0.5"
}
},
+ "chained-function": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/chained-function/-/chained-function-0.5.0.tgz",
+ "integrity": "sha1-JWS73994AxlL6/daayvQQe3iOzo="
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
"character-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
@@ -1971,12 +2210,16 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
- "dev": true,
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
+ "class-transformer": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.2.2.tgz",
+ "integrity": "sha512-wRKNNvdrSTOYut0ksO1/PTXFPTscbuGRB5R85FfdUuydGI+nXGPYeAByoifrfHFwadI2G4a7IQ5MXp9gnWNjFQ=="
+ },
"class-utils": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@@ -2023,6 +2266,16 @@
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
"integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
},
+ "cliui": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
"clone-deep": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz",
@@ -2127,33 +2380,33 @@
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
},
"compressible": {
- "version": "2.0.16",
- "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.16.tgz",
- "integrity": "sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA==",
+ "version": "2.0.17",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz",
+ "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==",
"dev": true,
"requires": {
- "mime-db": ">= 1.38.0 < 2"
+ "mime-db": ">= 1.40.0 < 2"
},
"dependencies": {
"mime-db": {
- "version": "1.38.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
- "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==",
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
"dev": true
}
}
},
"compression": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz",
- "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==",
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
+ "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"dev": true,
"requires": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
- "compressible": "~2.0.14",
+ "compressible": "~2.0.16",
"debug": "2.6.9",
- "on-headers": "~1.0.1",
+ "on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"vary": "~1.1.2"
}
@@ -2288,7 +2541,7 @@
},
"content-disposition": {
"version": "0.5.2",
- "resolved": "http://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
},
"content-type": {
@@ -2309,11 +2562,60 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
},
+ "cookie-parser": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
+ "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
+ "requires": {
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6"
+ }
+ },
+ "cookie-session": {
+ "version": "2.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.0.0-beta.3.tgz",
+ "integrity": "sha512-zyqm5tA0z9yMEB/xyP7lnRnqp8eLR2e0dap+9+rBwVigla9yPKn8XTL1jJymog8xjfrowqW2o5LUjixQChkqrw==",
+ "requires": {
+ "cookies": "0.7.1",
+ "debug": "3.1.0",
+ "on-headers": "~1.0.1",
+ "safe-buffer": "5.1.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
+ }
+ }
+ },
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
+ "cookies": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz",
+ "integrity": "sha1-fIphX1SBxhq58WyDNzG8uPZjuZs=",
+ "requires": {
+ "depd": "~1.1.1",
+ "keygrip": "~1.0.2"
+ }
+ },
"copy-concurrently": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
@@ -2380,7 +2682,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
- "dev": true,
"requires": {
"bn.js": "^4.1.0",
"elliptic": "^6.0.0"
@@ -2407,9 +2708,8 @@
},
"create-hash": {
"version": "1.2.0",
- "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
- "dev": true,
"requires": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
@@ -2420,9 +2720,8 @@
},
"create-hmac": {
"version": "1.1.7",
- "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
- "dev": true,
"requires": {
"cipher-base": "^1.0.3",
"create-hash": "^1.1.0",
@@ -2439,22 +2738,6 @@
"requires": {
"fbjs": "^0.8.0",
"gud": "^1.0.0"
- },
- "dependencies": {
- "fbjs": {
- "version": "0.8.17",
- "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
- "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
- "requires": {
- "core-js": "^1.0.0",
- "isomorphic-fetch": "^2.1.1",
- "loose-envify": "^1.0.0",
- "object-assign": "^4.1.0",
- "promise": "^7.1.1",
- "setimmediate": "^1.0.5",
- "ua-parser-js": "^0.7.18"
- }
- }
}
},
"crel": {
@@ -2462,11 +2745,19 @@
"resolved": "https://registry.npmjs.org/crel/-/crel-3.1.0.tgz",
"integrity": "sha512-VIGY44ERxx8lXVkOEfcB0A49OkjxkQNK+j+fHvoLy7GsGX1KKgAaQ+p9N0YgvQXu+X+ryUWGDeLx/fSI+w7+eg=="
},
+ "cross-spawn": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
+ "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "which": "^1.2.9"
+ }
+ },
"crypto-browserify": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
"integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
- "dev": true,
"requires": {
"browserify-cipher": "^1.0.0",
"browserify-sign": "^4.0.0",
@@ -2546,13 +2837,18 @@
},
"d": {
"version": "1.0.0",
- "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
"integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
"dev": true,
"requires": {
"es5-ext": "^0.10.9"
}
},
+ "d3-format": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz",
+ "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ=="
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -2567,6 +2863,22 @@
"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
"dev": true
},
+ "dateformat": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
+ "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=",
+ "dev": true,
+ "requires": {
+ "get-stdin": "^4.0.1",
+ "meow": "^3.3.0"
+ }
+ },
+ "debounce": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
+ "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==",
+ "dev": true
+ },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -2674,17 +2986,18 @@
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
},
"del": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz",
- "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
+ "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==",
"dev": true,
"requires": {
+ "@types/glob": "^7.1.1",
"globby": "^6.1.0",
- "is-path-cwd": "^1.0.0",
- "is-path-in-cwd": "^1.0.0",
- "p-map": "^1.1.1",
- "pify": "^3.0.0",
- "rimraf": "^2.2.8"
+ "is-path-cwd": "^2.0.0",
+ "is-path-in-cwd": "^2.0.0",
+ "p-map": "^2.0.0",
+ "pify": "^4.0.1",
+ "rimraf": "^2.6.3"
},
"dependencies": {
"globby": {
@@ -2729,7 +3042,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
"integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
- "dev": true,
"requires": {
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
@@ -2769,9 +3081,8 @@
},
"diffie-hellman": {
"version": "5.0.3",
- "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
- "dev": true,
"requires": {
"bn.js": "^4.1.0",
"miller-rabin": "^4.0.0",
@@ -2873,6 +3184,15 @@
"stream-shift": "^1.0.0"
}
},
+ "dynamic-dedupe": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
+ "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=",
+ "dev": true,
+ "requires": {
+ "xtend": "^4.0.0"
+ }
+ },
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -2904,7 +3224,6 @@
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
"integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
- "dev": true,
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
@@ -3172,7 +3491,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
"integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
- "dev": true,
"requires": {
"md5.js": "^1.3.4",
"safe-buffer": "^5.1.1"
@@ -3312,7 +3630,7 @@
"dependencies": {
"array-flatten": {
"version": "1.1.1",
- "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"statuses": {
@@ -3467,6 +3785,20 @@
"websocket-driver": ">=0.5.1"
}
},
+ "fbjs": {
+ "version": "0.8.17",
+ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
+ "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
+ "requires": {
+ "core-js": "^1.0.0",
+ "isomorphic-fetch": "^2.1.1",
+ "loose-envify": "^1.0.0",
+ "object-assign": "^4.1.0",
+ "promise": "^7.1.1",
+ "setimmediate": "^1.0.5",
+ "ua-parser-js": "^0.7.18"
+ }
+ },
"figgy-pudding": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
@@ -3483,6 +3815,15 @@
"schema-utils": "^1.0.0"
}
},
+ "filewatcher": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/filewatcher/-/filewatcher-3.0.1.tgz",
+ "integrity": "sha1-9KGVc1Xdr0Q8zXiolfPVXiPIoDQ=",
+ "dev": true,
+ "requires": {
+ "debounce": "^1.0.0"
+ }
+ },
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@@ -3506,7 +3847,7 @@
},
"finalhandler": {
"version": "1.1.1",
- "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
"requires": {
"debug": "2.6.9",
@@ -3541,6 +3882,15 @@
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
},
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "requires": {
+ "path-exists": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
"findup-sync": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
@@ -3613,15 +3963,14 @@
"requires": {
"ms": "^2.1.1"
}
- },
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
- "dev": true
}
}
},
+ "font-awesome": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
+ "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
+ },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -3641,6 +3990,53 @@
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
+ "fork-ts-checker-webpack-plugin": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.3.3.tgz",
+ "integrity": "sha512-XrSkBRxE3MfCSFfMSmbEI/LvCYV9UPUWN1RDfF6r5jXnrhR5qzqxEHxjCwQlnN/uH7IlSfnaVtD3FLXQBznX5w==",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "^6.22.0",
+ "chalk": "^2.4.1",
+ "chokidar": "^2.0.4",
+ "micromatch": "^3.1.10",
+ "minimatch": "^3.0.4",
+ "semver": "^5.6.0",
+ "tapable": "^1.0.0",
+ "worker-rpc": "^0.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@@ -3741,14 +4137,12 @@
"balanced-match": {
"version": "1.0.0",
"resolved": false,
- "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
- "optional": true
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": false,
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3768,8 +4162,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": false,
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "optional": true
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"console-control-strings": {
"version": "1.1.0",
@@ -3917,7 +4310,6 @@
"version": "3.0.4",
"resolved": false,
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -4234,9 +4626,9 @@
}
},
"fstream": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
- "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
+ "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
@@ -4262,18 +4654,6 @@
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
- },
- "dependencies": {
- "string-width": {
- "version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- }
}
},
"gaze": {
@@ -4458,6 +4838,12 @@
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true
},
+ "growly": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
+ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
+ "dev": true
+ },
"gud": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
@@ -4568,7 +4954,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
"integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
- "dev": true,
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
@@ -4578,7 +4963,6 @@
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
- "dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
@@ -4594,7 +4978,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
- "dev": true,
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
@@ -4654,7 +5037,7 @@
},
"http-errors": {
"version": "1.6.3",
- "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"requires": {
"depd": "~1.1.2",
@@ -4681,9 +5064,9 @@
},
"dependencies": {
"eventemitter3": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
- "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
+ "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==",
"dev": true
}
}
@@ -4910,12 +5293,12 @@
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"internal-ip": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.2.0.tgz",
- "integrity": "sha512-ZY8Rk+hlvFeuMmG5uH1MXhhdeMntmIaxaInvAmzMq/SHV8rv4Kh+6GiQNNDQd0wZFrcO+FiTBo8lui/osKOyJw==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
+ "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==",
"dev": true,
"requires": {
- "default-gateway": "^4.0.1",
+ "default-gateway": "^4.2.0",
"ipaddr.js": "^1.9.0"
},
"dependencies": {
@@ -4933,6 +5316,14 @@
"integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
"dev": true
},
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"invert-kv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
@@ -4957,7 +5348,7 @@
},
"is-accessor-descriptor": {
"version": "0.1.6",
- "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"requires": {
"kind-of": "^3.0.2"
@@ -5001,7 +5392,7 @@
},
"is-data-descriptor": {
"version": "0.1.4",
- "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"requires": {
"kind-of": "^3.0.2"
@@ -5127,18 +5518,29 @@
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
},
"is-path-cwd": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
- "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.1.0.tgz",
+ "integrity": "sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw==",
"dev": true
},
"is-path-in-cwd": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
- "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz",
+ "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==",
"dev": true,
"requires": {
- "is-path-inside": "^1.0.0"
+ "is-path-inside": "^2.1.0"
+ },
+ "dependencies": {
+ "is-path-inside": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz",
+ "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==",
+ "dev": true,
+ "requires": {
+ "path-is-inside": "^1.0.2"
+ }
+ }
}
},
"is-path-inside": {
@@ -5446,6 +5848,16 @@
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.0.tgz",
"integrity": "sha512-6hHxsp9e6zQU8nXsP+02HGWXwTkOEw6IROhF2ZA28cYbUk4eJ6QbtZvdqZOdD9YPKghG3apk5eOCvs+tLl3lRg=="
},
+ "keycode": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
+ "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
+ },
+ "keygrip": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz",
+ "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g=="
+ },
"killable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -5480,7 +5892,7 @@
},
"load-json-file": {
"version": "1.1.0",
- "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"requires": {
"graceful-fs": "^4.1.2",
@@ -5500,7 +5912,7 @@
},
"pify": {
"version": "2.3.0",
- "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
}
}
@@ -5544,16 +5956,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
- "lodash.assign": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
- "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
- },
- "lodash.clonedeep": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
- },
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
@@ -5589,11 +5991,6 @@
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
- "lodash.mergewith": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
- "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ=="
- },
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
@@ -5772,7 +6169,6 @@
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
- "dev": true,
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1",
@@ -5781,7 +6177,7 @@
},
"media-typer": {
"version": "0.3.0",
- "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"mem": {
@@ -5813,7 +6209,7 @@
},
"meow": {
"version": "3.7.0",
- "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
"integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
"requires": {
"camelcase-keys": "^2.0.0",
@@ -5843,6 +6239,12 @@
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
+ "microevent.ts": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz",
+ "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==",
+ "dev": true
+ },
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@@ -5867,7 +6269,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
"integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
- "dev": true,
"requires": {
"bn.js": "^4.0.0",
"brorand": "^1.0.1"
@@ -5900,14 +6301,12 @@
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
- "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
- "dev": true
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
- "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
- "dev": true
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"minimatch": {
"version": "3.0.4",
@@ -5979,7 +6378,7 @@
},
"mkdirp": {
"version": "0.5.1",
- "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": {
"minimist": "0.0.8"
@@ -5992,6 +6391,11 @@
}
}
},
+ "mobile-detect": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/mobile-detect/-/mobile-detect-1.4.3.tgz",
+ "integrity": "sha512-UaahPNLllQsstHOEHAmVnTHCMQrAS9eL5Qgdi50QrYz6UgGk+Xziz2udz2GN6NYcyODcPLnasC7a7s6R2DjiaQ=="
+ },
"mobx": {
"version": "5.9.0",
"resolved": "https://registry.npmjs.org/mobx/-/mobx-5.9.0.tgz",
@@ -6116,6 +6520,14 @@
"sliced": "1.0.1"
},
"dependencies": {
+ "async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
+ "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
+ "requires": {
+ "lodash": "^4.17.10"
+ }
+ },
"bson": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz",
@@ -6219,7 +6631,8 @@
"nan": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz",
- "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw=="
+ "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==",
+ "optional": true
},
"nanomatch": {
"version": "1.2.13",
@@ -6257,7 +6670,7 @@
},
"next-tick": {
"version": "1.0.0",
- "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true
},
@@ -6306,28 +6719,10 @@
"which": "1"
},
"dependencies": {
- "nopt": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
- "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
- "requires": {
- "abbrev": "1"
- }
- },
"semver": {
"version": "5.3.0",
- "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
- },
- "tar": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
- "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
- "requires": {
- "block-stream": "*",
- "fstream": "^1.0.2",
- "inherits": "2"
- }
}
}
},
@@ -6362,10 +6757,23 @@
"vm-browserify": "0.0.4"
}
},
+ "node-notifier": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz",
+ "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==",
+ "dev": true,
+ "requires": {
+ "growly": "^1.3.0",
+ "is-wsl": "^1.1.0",
+ "semver": "^5.5.0",
+ "shellwords": "^0.1.1",
+ "which": "^1.3.0"
+ }
+ },
"node-sass": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz",
- "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz",
+ "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==",
"requires": {
"async-foreach": "^0.1.3",
"chalk": "^1.1.1",
@@ -6374,12 +6782,10 @@
"get-stdin": "^4.0.1",
"glob": "^7.0.3",
"in-publish": "^2.0.0",
- "lodash.assign": "^4.2.0",
- "lodash.clonedeep": "^4.3.2",
- "lodash.mergewith": "^4.6.0",
+ "lodash": "^4.17.11",
"meow": "^3.7.0",
"mkdirp": "^0.5.1",
- "nan": "^2.10.0",
+ "nan": "^2.13.2",
"node-gyp": "^3.8.0",
"npmlog": "^4.0.0",
"request": "^2.88.0",
@@ -6388,39 +6794,18 @@
"true-case-path": "^1.0.2"
},
"dependencies": {
- "ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
- },
- "chalk": {
- "version": "1.1.3",
- "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "requires": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- }
- },
- "cross-spawn": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
- "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
- "requires": {
- "lru-cache": "^4.0.1",
- "which": "^1.2.9"
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+ "nan": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
}
}
},
+ "nodemailer": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-5.1.1.tgz",
+ "integrity": "sha512-hKGCoeNdFL2W7S76J/Oucbw0/qRlfG815tENdhzcqTpSjKgAN91mFOqU2lQUflRRxFM7iZvCyaFcAR9noc/CqQ=="
+ },
"nodemon": {
"version": "1.18.10",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.10.tgz",
@@ -6485,6 +6870,14 @@
}
}
},
+ "nopt": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+ "requires": {
+ "abbrev": "1"
+ }
+ },
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -9669,9 +10062,9 @@
}
},
"opn": {
- "version": "5.4.0",
- "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz",
- "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
+ "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==",
"dev": true,
"requires": {
"is-wsl": "^1.1.0"
@@ -9699,12 +10092,20 @@
},
"os-homedir": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
},
+ "os-locale": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+ "requires": {
+ "lcid": "^1.0.0"
+ }
+ },
"os-tmpdir": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"osenv": {
@@ -9752,9 +10153,9 @@
}
},
"p-map": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
- "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
"dev": true
},
"p-try": {
@@ -9795,7 +10196,6 @@
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz",
"integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==",
- "dev": true,
"requires": {
"asn1.js": "^4.0.0",
"browserify-aes": "^1.0.0",
@@ -9870,7 +10270,7 @@
},
"path-browserify": {
"version": "0.0.0",
- "resolved": "http://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
"integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
"dev": true
},
@@ -9889,7 +10289,7 @@
},
"path-is-absolute": {
"version": "1.0.1",
- "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-is-inside": {
@@ -9912,6 +10312,23 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+ }
+ }
+ },
"pathval": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
@@ -9927,7 +10344,6 @@
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
"integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
- "dev": true,
"requires": {
"create-hash": "^1.1.2",
"create-hmac": "^1.1.4",
@@ -9951,9 +10367,9 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"pify": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
- "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
},
"pinkie": {
@@ -9989,6 +10405,11 @@
}
}
},
+ "popper.js": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz",
+ "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA=="
+ },
"portfinder": {
"version": "1.0.20",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz",
@@ -10174,6 +10595,15 @@
"react-is": "^16.8.1"
}
},
+ "prop-types-extra": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.0.tgz",
+ "integrity": "sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg==",
+ "requires": {
+ "react-is": "^16.3.2",
+ "warning": "^3.0.0"
+ }
+ },
"prosemirror-commands": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.0.7.tgz",
@@ -10346,7 +10776,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
"integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
- "dev": true,
"requires": {
"bn.js": "^4.1.0",
"browserify-rsa": "^4.0.0",
@@ -10520,9 +10949,9 @@
"dev": true
},
"querystringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz",
- "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
"dev": true
},
"random-bytes": {
@@ -10534,7 +10963,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
"requires": {
"safe-buffer": "^5.1.0"
}
@@ -10543,7 +10971,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
"integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
- "dev": true,
"requires": {
"randombytes": "^2.0.5",
"safe-buffer": "^5.1.0"
@@ -10611,6 +11038,70 @@
"scheduler": "^0.13.4"
}
},
+ "react-bootstrap": {
+ "version": "1.0.0-beta.8",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.0.0-beta.8.tgz",
+ "integrity": "sha512-rdCJbjBMIVzjeKrploQJMpmpVkndsPDFH+NBGM5npefL+oA5WBEzURgllWLbKdb3mmuuJamilt4j7+Dg7yTxBQ==",
+ "requires": {
+ "@babel/runtime": "^7.4.2",
+ "@react-bootstrap/react-popper": "1.2.1",
+ "@restart/context": "^2.1.4",
+ "@restart/hooks": "^0.2.3",
+ "classnames": "^2.2.6",
+ "dom-helpers": "^3.4.0",
+ "invariant": "^2.2.4",
+ "keycode": "^2.2.0",
+ "popper.js": "^1.14.7",
+ "prop-types": "^15.7.2",
+ "prop-types-extra": "^1.1.0",
+ "react-overlays": "^1.2.0",
+ "react-transition-group": "^2.7.1",
+ "uncontrollable": "^6.1.0",
+ "warning": "^4.0.3"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz",
+ "integrity": "sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "react-transition-group": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
+ "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
+ "requires": {
+ "dom-helpers": "^3.4.0",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
+ "uncontrollable": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-6.1.0.tgz",
+ "integrity": "sha512-2TzEm0pLKauMBZfAZXsgQvLpZHEp95891frCZdGDrSG7dWYaIQhedwLAzi0X8pR8KHNqlmuYEb2cEgbQzr050A==",
+ "requires": {
+ "invariant": "^2.2.4"
+ }
+ },
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
+ "react-bootstrap-dropdown-menu": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/react-bootstrap-dropdown-menu/-/react-bootstrap-dropdown-menu-1.1.15.tgz",
+ "integrity": "sha512-o35fODF4GsNxEzmnRWZuoe29a6x3fXqRsLXOlAvS4d+TvO3yoLTM8iZnSZpJCkHQnjOOWRnGvDh5tryqJCKZ0w=="
+ },
"react-color": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.0.tgz",
@@ -10624,6 +11115,11 @@
"tinycolor2": "^1.4.1"
}
},
+ "react-context-toolbox": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/react-context-toolbox/-/react-context-toolbox-2.0.2.tgz",
+ "integrity": "sha512-tY4j0imkYC3n5ZlYSgFkaw7fmlCp3IoQQ6DxpqeNHzcD0hf+6V+/HeJxviLUZ1Rv1Yn3N3xyO2EhkkZwHn0m1A=="
+ },
"react-dimensions": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz",
@@ -10677,9 +11173,9 @@
"integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA=="
},
"react-jsx-parser": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-1.14.1.tgz",
- "integrity": "sha512-kbgL6hHIr/7Pbt/+aePRmLB1ZWNllLXyeX4lk9ERrsPJVd9aOkhZDQCL3qIDvEY1Sy/6EeaQIKF3TQnVuiZFrQ==",
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-1.16.1.tgz",
+ "integrity": "sha512-X9vD27AJgOSk4vAb8tnJEJXz6yxbtKhm/yfzfNP4I8+SmUYDSWBCJEVjh0vXq+muPTGfyfvVN3+vVmZFUVfMNg==",
"requires": {
"acorn-jsx": "^4.1.1"
}
@@ -10720,6 +11216,39 @@
"threads": "^0.8.0"
}
},
+ "react-overlays": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-1.2.0.tgz",
+ "integrity": "sha512-i/FCV8wR6aRaI+Kz/dpJhOdyx+ah2tN1RhT9InPrexyC4uzf3N4bNayFTGtUeQVacj57j1Mqh1CwV60/5153Iw==",
+ "requires": {
+ "classnames": "^2.2.6",
+ "dom-helpers": "^3.4.0",
+ "prop-types": "^15.6.2",
+ "prop-types-extra": "^1.1.0",
+ "react-context-toolbox": "^2.0.2",
+ "react-popper": "^1.3.2",
+ "uncontrollable": "^6.0.0",
+ "warning": "^4.0.2"
+ },
+ "dependencies": {
+ "uncontrollable": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-6.1.0.tgz",
+ "integrity": "sha512-2TzEm0pLKauMBZfAZXsgQvLpZHEp95891frCZdGDrSG7dWYaIQhedwLAzi0X8pR8KHNqlmuYEb2cEgbQzr050A==",
+ "requires": {
+ "invariant": "^2.2.4"
+ }
+ },
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
"react-pdf": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-4.0.5.tgz",
@@ -10797,6 +11326,43 @@
"resolved": "https://registry.npmjs.org/react-pointable/-/react-pointable-1.1.3.tgz",
"integrity": "sha512-7skalWy38NLyKk1HcITmuloqCe2INPh69cFHbN7NcCn+Wfb0Ha4XGwGN0iVa4ZzqIHSqFRYOmV0loYNHrZ5/Sg=="
},
+ "react-popper": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.3.tgz",
+ "integrity": "sha512-ynMZBPkXONPc5K4P5yFWgZx5JGAUIP3pGGLNs58cfAPgK67olx7fmLp+AdpZ0+GoQ+ieFDa/z4cdV6u7sioH6w==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "create-react-context": "<=0.2.2",
+ "popper.js": "^1.14.4",
+ "prop-types": "^15.6.1",
+ "typed-styles": "^0.0.7",
+ "warning": "^4.0.2"
+ },
+ "dependencies": {
+ "create-react-context": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.2.tgz",
+ "integrity": "sha512-KkpaLARMhsTsgp0d2NA/R94F/eDLbhXERdIq3LvX2biCAXcDvHYoOqHfWCHf1+OLj+HKBotLG3KqaOOf+C1C+A==",
+ "requires": {
+ "fbjs": "^0.8.0",
+ "gud": "^1.0.0"
+ }
+ },
+ "typed-styles": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
+ "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
+ },
+ "warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+ },
"react-rnd": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-7.4.3.tgz",
@@ -10806,6 +11372,15 @@
"react-draggable": "^3.0.5"
}
},
+ "react-simple-dropdown": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/react-simple-dropdown/-/react-simple-dropdown-3.2.3.tgz",
+ "integrity": "sha512-NmyyvA0D4wph5ctzkn8U4wmblOacavJMl9gTOhQR3v8I997mc1FL1NFKkj3Mx+HNysBKRD/HI+kpxXCAgXumPw==",
+ "requires": {
+ "classnames": "^2.1.2",
+ "prop-types": "^15.5.8"
+ }
+ },
"react-split-pane": {
"version": "0.1.85",
"resolved": "https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.85.tgz",
@@ -10861,23 +11436,6 @@
"load-json-file": "^1.0.0",
"normalize-package-data": "^2.3.2",
"path-type": "^1.0.0"
- },
- "dependencies": {
- "path-type": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
- "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
- "requires": {
- "graceful-fs": "^4.1.2",
- "pify": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- },
- "pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
- }
}
},
"read-pkg-up": {
@@ -10887,22 +11445,11 @@
"requires": {
"find-up": "^1.0.0",
"read-pkg": "^1.0.0"
- },
- "dependencies": {
- "find-up": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
- "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
- "requires": {
- "path-exists": "^2.0.0",
- "pinkie-promise": "^2.0.0"
- }
- }
}
},
"readable-stream": {
"version": "2.3.6",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
@@ -10956,6 +11503,11 @@
"strip-indent": "^1.0.1"
}
},
+ "regenerator-runtime": {
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
+ "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
+ },
"regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -11037,6 +11589,25 @@
"uuid": "^3.3.2"
}
},
+ "request-promise": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz",
+ "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==",
+ "requires": {
+ "bluebird": "^3.5.0",
+ "request-promise-core": "1.1.2",
+ "stealthy-require": "^1.1.1",
+ "tough-cookie": "^2.3.3"
+ }
+ },
+ "request-promise-core": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
+ "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
+ "requires": {
+ "lodash": "^4.17.11"
+ }
+ },
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -11136,7 +11707,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
- "dev": true,
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1"
@@ -11163,7 +11733,7 @@
},
"safe-regex": {
"version": "1.1.0",
- "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"requires": {
"ret": "~0.1.10"
@@ -11321,6 +11891,11 @@
"integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==",
"dev": true
},
+ "serializr": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/serializr/-/serializr-1.5.1.tgz",
+ "integrity": "sha512-ygrOOOB+eaYYiFCLSS1kzu2KtmhP1ZzLFsv+GPWIdhtuDMBTN3H0ldnswPLqIExrEyQWQhiExFlC9Gu8pCT9Uw=="
+ },
"serve-index": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
@@ -11385,9 +11960,8 @@
},
"sha.js": {
"version": "2.4.11",
- "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
- "dev": true,
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
@@ -11425,6 +11999,12 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
+ "shellwords": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
+ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
+ "dev": true
+ },
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@@ -11681,6 +12261,23 @@
}
}
},
+ "solr-node": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/solr-node/-/solr-node-1.2.1.tgz",
+ "integrity": "sha512-DN3+FSBgpJEgGTNddzS8tNb+ILSn5MLcsWf15G9rGxi/sROHbpcevdRSVx6s5/nz56c/5AnBTBZWak7IXWX97A==",
+ "requires": {
+ "@log4js-node/log4js-api": "^1.0.2",
+ "node-fetch": "^2.3.0",
+ "underscore": "^1.8.3"
+ },
+ "dependencies": {
+ "node-fetch": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
+ "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
+ }
+ }
+ },
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -11763,9 +12360,9 @@
}
},
"spdx-license-ids": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz",
- "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g=="
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz",
+ "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA=="
},
"spdy": {
"version": "4.0.0",
@@ -11815,9 +12412,9 @@
}
},
"readable-stream": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz",
- "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz",
+ "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
@@ -11897,6 +12494,11 @@
"readable-stream": "^2.0.1"
}
},
+ "stealthy-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
+ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
+ },
"stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
@@ -11937,37 +12539,18 @@
"dev": true
},
"string-width": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^4.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
- },
- "is-fullwidth-code-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
- },
- "strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
- "requires": {
- "ansi-regex": "^3.0.0"
- }
- }
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
}
},
"string_decoder": {
"version": "1.1.1",
- "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
@@ -11975,7 +12558,7 @@
},
"strip-ansi": {
"version": "3.0.1",
- "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
@@ -11991,7 +12574,7 @@
},
"strip-eof": {
"version": "1.0.0",
- "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"strip-indent": {
@@ -12017,12 +12600,27 @@
"schema-utils": "^1.0.0"
}
},
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+ },
"tapable": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz",
"integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==",
"dev": true
},
+ "tar": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
+ "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
+ "requires": {
+ "block-stream": "*",
+ "fstream": "^1.0.12",
+ "inherits": "2"
+ }
+ },
"term-size": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
@@ -12351,6 +12949,12 @@
"repeat-string": "^1.6.1"
}
},
+ "toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+ "dev": true
+ },
"token-stream": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz",
@@ -12390,6 +12994,12 @@
}
}
},
+ "tree-kill": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz",
+ "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==",
+ "dev": true
+ },
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
@@ -12403,6 +13013,50 @@
"glob": "^7.1.2"
}
},
+ "ts-loader": {
+ "version": "5.4.5",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.4.5.tgz",
+ "integrity": "sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.3.0",
+ "enhanced-resolve": "^4.0.0",
+ "loader-utils": "^1.0.2",
+ "micromatch": "^3.1.4",
+ "semver": "^5.0.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
"ts-node": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
@@ -12419,15 +13073,138 @@
"yn": "^2.0.0"
}
},
+ "ts-node-dev": {
+ "version": "1.0.0-pre.39",
+ "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.0.0-pre.39.tgz",
+ "integrity": "sha512-yOg9nMAi6U2HcAkhnFuWxfg53XDqpdbeBESo+7DfmlDpQX4RrEzBNV6szOjlm/OH0KiRgG5J0emvg/BS/gQmXQ==",
+ "dev": true,
+ "requires": {
+ "dateformat": "~1.0.4-1.2.3",
+ "dynamic-dedupe": "^0.3.0",
+ "filewatcher": "~3.0.0",
+ "minimist": "^1.1.3",
+ "mkdirp": "^0.5.1",
+ "node-notifier": "^5.4.0",
+ "resolve": "^1.0.0",
+ "rimraf": "^2.6.1",
+ "tree-kill": "^1.2.1",
+ "ts-node": "*",
+ "tsconfig": "^7.0.0"
+ }
+ },
+ "tsconfig": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
+ "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==",
+ "dev": true,
+ "requires": {
+ "@types/strip-bom": "^3.0.0",
+ "@types/strip-json-comments": "0.0.30",
+ "strip-bom": "^3.0.0",
+ "strip-json-comments": "^2.0.0"
+ },
+ "dependencies": {
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true
+ }
+ }
+ },
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
},
+ "tslint": {
+ "version": "5.16.0",
+ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz",
+ "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "builtin-modules": "^1.1.1",
+ "chalk": "^2.3.0",
+ "commander": "^2.12.1",
+ "diff": "^3.2.0",
+ "glob": "^7.1.1",
+ "js-yaml": "^3.13.0",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "resolve": "^1.3.2",
+ "semver": "^5.3.0",
+ "tslib": "^1.8.0",
+ "tsutils": "^2.29.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "tslint-loader": {
+ "version": "3.5.4",
+ "resolved": "https://registry.npmjs.org/tslint-loader/-/tslint-loader-3.5.4.tgz",
+ "integrity": "sha512-jBHNNppXut6SgZ7CsTBh+6oMwVum9n8azbmcYSeMlsABhWWoHwjq631vIFXef3VSd75cCdX3rc6kstsB7rSVVw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "object-assign": "^4.1.1",
+ "rimraf": "^2.4.4",
+ "semver": "^5.3.0"
+ }
+ },
+ "tsutils": {
+ "version": "2.29.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
"tty-browserify": {
"version": "0.0.0",
- "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
"dev": true
},
@@ -12459,6 +13236,11 @@
"mime-types": "~2.1.18"
}
},
+ "typed-styles": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.5.tgz",
+ "integrity": "sha512-ht+rEe5UsdEBAa3gr64+QjUOqjOLJfWLvl5HZR5Ev9uo/OnD3p43wPeFSB1hNFc13GXQF/JU1Bn0YHLUqBRIlw=="
+ },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -12466,9 +13248,14 @@
"dev": true
},
"typescript": {
- "version": "3.3.3333",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz",
- "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw=="
+ "version": "3.4.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz",
+ "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw=="
+ },
+ "typescript-collections": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/typescript-collections/-/typescript-collections-1.3.2.tgz",
+ "integrity": "sha512-Frfvtwym0VebbueXWEJlVkGiWjKEFStsRwusuzjh8lX8OEJ9ZbFqpYLNfPvZcxw/+nqW0cRNBeBq6SVoTjymcQ=="
},
"ua-parser-js": {
"version": "0.7.19",
@@ -12532,6 +13319,14 @@
"random-bytes": "~1.0.0"
}
},
+ "uncontrollable": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-5.1.0.tgz",
+ "integrity": "sha512-5FXYaFANKaafg4IVZXUNtGyzsnYEvqlr9wQ3WpZxFpEUxl29A3H6Q4G1Dnnorvq9TGOGATBApWR4YpLAh+F5hw==",
+ "requires": {
+ "invariant": "^2.2.4"
+ }
+ },
"undefsafe": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz",
@@ -12540,6 +13335,11 @@
"debug": "^2.2.0"
}
},
+ "underscore": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
+ "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
+ },
"union-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
@@ -12749,12 +13549,12 @@
}
},
"url-parse": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz",
- "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==",
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
+ "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"dev": true,
"requires": {
- "querystringify": "^2.0.0",
+ "querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
@@ -12832,7 +13632,7 @@
},
"vm-browserify": {
"version": "0.0.4",
- "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
"integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
"dev": true,
"requires": {
@@ -13164,55 +13964,132 @@
}
},
"webpack-dev-server": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.2.1.tgz",
- "integrity": "sha512-sjuE4mnmx6JOh9kvSbPYw3u/6uxCLHNWfhWaIPwcXWsvWOPN+nc5baq4i9jui3oOBRXGonK9+OI0jVkaz6/rCw==",
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.4.1.tgz",
+ "integrity": "sha512-CRqZQX2ryMtrg0r3TXQPpNh76eM1HD3Wmu6zDBxIKi/d2y+4aa28Ia8weNT0bfgWpY6Vs3Oq/K8+DjfbR+tWYw==",
"dev": true,
"requires": {
"ansi-html": "0.0.7",
"bonjour": "^3.5.0",
- "chokidar": "^2.0.0",
- "compression": "^1.5.2",
- "connect-history-api-fallback": "^1.3.0",
+ "chokidar": "^2.1.6",
+ "compression": "^1.7.4",
+ "connect-history-api-fallback": "^1.6.0",
"debug": "^4.1.1",
- "del": "^3.0.0",
- "express": "^4.16.2",
- "html-entities": "^1.2.0",
+ "del": "^4.1.1",
+ "express": "^4.17.0",
+ "html-entities": "^1.2.1",
"http-proxy-middleware": "^0.19.1",
"import-local": "^2.0.0",
- "internal-ip": "^4.2.0",
+ "internal-ip": "^4.3.0",
"ip": "^1.1.5",
- "killable": "^1.0.0",
- "loglevel": "^1.4.1",
- "opn": "^5.1.0",
- "portfinder": "^1.0.9",
+ "killable": "^1.0.1",
+ "loglevel": "^1.6.1",
+ "opn": "^5.5.0",
+ "portfinder": "^1.0.20",
"schema-utils": "^1.0.0",
- "selfsigned": "^1.9.1",
- "semver": "^5.6.0",
- "serve-index": "^1.7.2",
+ "selfsigned": "^1.10.4",
+ "semver": "^6.0.0",
+ "serve-index": "^1.9.1",
"sockjs": "0.3.19",
"sockjs-client": "1.3.0",
"spdy": "^4.0.0",
- "strip-ansi": "^3.0.0",
+ "strip-ansi": "^3.0.1",
"supports-color": "^6.1.0",
"url": "^0.11.0",
- "webpack-dev-middleware": "^3.5.1",
+ "webpack-dev-middleware": "^3.7.0",
"webpack-log": "^2.0.0",
- "yargs": "12.0.2"
+ "yargs": "12.0.5"
},
"dependencies": {
+ "accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "dev": true,
+ "requires": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ }
+ },
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+ "dev": true
+ },
+ "body-parser": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+ "dev": true,
+ "requires": {
+ "bytes": "3.1.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "on-finished": "~2.3.0",
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "bytes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+ "dev": true
+ },
"camelcase": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
- "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
+ "chokidar": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz",
+ "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==",
+ "dev": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
"cliui": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
@@ -13235,6 +14112,21 @@
}
}
},
+ "content-disposition": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "cookie": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+ "dev": true
+ },
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -13244,13 +14136,91 @@
"ms": "^2.1.1"
}
},
- "decamelize": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz",
- "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==",
+ "express": {
+ "version": "4.17.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.17.0.tgz",
+ "integrity": "sha512-1Z7/t3Z5ZnBG252gKUPyItc4xdeaA0X934ca2ewckAsVsw9EG71i++ZHZPYnus8g/s5Bty8IMpSVEuRkmwwPRQ==",
"dev": true,
"requires": {
- "xregexp": "4.0.0"
+ "accepts": "~1.3.7",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.19.0",
+ "content-disposition": "0.5.3",
+ "content-type": "~1.0.4",
+ "cookie": "0.4.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.1.2",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.5",
+ "qs": "6.7.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.1.2",
+ "send": "0.17.1",
+ "serve-static": "1.14.1",
+ "setprototypeof": "1.1.1",
+ "statuses": "~1.5.0",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
}
},
"find-up": {
@@ -13262,12 +14232,31 @@
"locate-path": "^3.0.0"
}
},
+ "http-errors": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ }
+ },
"invert-kv": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
"integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
"dev": true
},
+ "ipaddr.js": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+ "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==",
+ "dev": true
+ },
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
@@ -13293,10 +14282,37 @@
"path-exists": "^3.0.0"
}
},
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true
+ },
+ "mime-db": {
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+ "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+ "dev": true,
+ "requires": {
+ "mime-db": "1.40.0"
+ }
+ },
+ "negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"os-locale": {
@@ -13329,9 +14345,15 @@
}
},
"p-try": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
- "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"dev": true
},
"path-exists": {
@@ -13340,6 +14362,104 @@
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
+ "proxy-addr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+ "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+ "dev": true,
+ "requires": {
+ "forwarded": "~0.1.2",
+ "ipaddr.js": "1.9.0"
+ }
+ },
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+ "dev": true
+ },
+ "range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true
+ },
+ "raw-body": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+ "dev": true,
+ "requires": {
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz",
+ "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==",
+ "dev": true
+ },
+ "send": {
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "destroy": "~1.0.4",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "~1.7.2",
+ "mime": "1.6.0",
+ "ms": "2.1.1",
+ "on-finished": "~2.3.0",
+ "range-parser": "~1.2.1",
+ "statuses": "~1.5.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+ "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+ "dev": true,
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.17.1"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
+ "dev": true
+ },
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@@ -13370,6 +14490,42 @@
"has-flag": "^3.0.0"
}
},
+ "type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dev": true,
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ }
+ },
+ "upath": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz",
+ "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==",
+ "dev": true
+ },
+ "webpack-dev-middleware": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz",
+ "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==",
+ "dev": true,
+ "requires": {
+ "memory-fs": "^0.4.1",
+ "mime": "^2.4.2",
+ "range-parser": "^1.2.1",
+ "webpack-log": "^2.0.0"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz",
+ "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==",
+ "dev": true
+ }
+ }
+ },
"webpack-log": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
@@ -13381,13 +14537,13 @@
}
},
"yargs": {
- "version": "12.0.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz",
- "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==",
+ "version": "12.0.5",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+ "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
"dev": true,
"requires": {
"cliui": "^4.0.0",
- "decamelize": "^2.0.0",
+ "decamelize": "^1.2.0",
"find-up": "^3.0.0",
"get-caller-file": "^1.0.1",
"os-locale": "^3.0.0",
@@ -13397,16 +14553,17 @@
"string-width": "^2.0.0",
"which-module": "^2.0.0",
"y18n": "^3.2.1 || ^4.0.0",
- "yargs-parser": "^10.1.0"
+ "yargs-parser": "^11.1.1"
}
},
"yargs-parser": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
- "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+ "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
"dev": true,
"requires": {
- "camelcase": "^4.1.0"
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
}
}
}
@@ -13619,9 +14776,18 @@
}
}
},
+ "worker-rpc": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz",
+ "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==",
+ "dev": true,
+ "requires": {
+ "microevent.ts": "~0.1.1"
+ }
+ },
"wrap-ansi": {
"version": "2.1.0",
- "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"requires": {
"string-width": "^1.0.1",
@@ -13630,7 +14796,7 @@
"dependencies": {
"string-width": {
"version": "1.0.2",
- "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "^1.0.0",
@@ -13673,11 +14839,10 @@
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
},
- "xregexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
- "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==",
- "dev": true
+ "xoauth2": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/xoauth2/-/xoauth2-1.2.0.tgz",
+ "integrity": "sha1-8u76wRRyyXHqO8RuVU60sSMhRuU="
},
"xtend": {
"version": "4.0.1",
@@ -13722,71 +14887,6 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
},
- "cliui": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
- "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
- "requires": {
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wrap-ansi": "^2.0.0"
- }
- },
- "find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "requires": {
- "locate-path": "^3.0.0"
- }
- },
- "locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "requires": {
- "p-locate": "^3.0.0"
- }
- },
- "os-locale": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
- "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
- "requires": {
- "lcid": "^1.0.0"
- }
- },
- "p-limit": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
- "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
- "requires": {
- "p-try": "^2.0.0"
- }
- },
- "p-locate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "requires": {
- "p-limit": "^2.0.0"
- }
- },
- "p-try": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
- "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ=="
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
"which-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
@@ -13796,14 +14896,21 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
- },
- "yargs-parser": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
- "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
- "requires": {
- "camelcase": "^3.0.0"
- }
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
+ "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+ "requires": {
+ "camelcase": "^3.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
}
}
},
diff --git a/package.json b/package.json
index 6509fa449..147f59c25 100644
--- a/package.json
+++ b/package.json
@@ -1,139 +1,176 @@
{
- "name": "dash",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "start": "nodemon --watch src/server/**/*.ts --exec ts-node src/server/index.ts",
- "build": "webpack --env production",
- "test": "mocha -r ts-node/register test/**/*.ts",
- "tsc": "tsc"
- },
- "devDependencies": {
- "@fortawesome/fontawesome-free": "^5.7.2",
- "@types/chai": "^4.1.7",
- "@types/mocha": "^5.2.6",
- "@types/react-dom": "^16.8.2",
- "@types/webpack-dev-middleware": "^2.0.2",
- "@types/webpack-hot-middleware": "^2.16.4",
- "awesome-typescript-loader": "^5.2.1",
- "chai": "^4.2.0",
- "copy-webpack-plugin": "^4.6.0",
- "css-loader": "^2.1.1",
- "file-loader": "^3.0.1",
- "mocha": "^5.2.0",
- "sass-loader": "^7.1.0",
- "scss-loader": "0.0.1",
- "style-loader": "^0.23.1",
- "ts-node": "^7.0.1",
- "typescript": "^3.3.3333",
- "webpack": "^4.29.6",
- "webpack-cli": "^3.2.3",
- "webpack-dev-middleware": "^3.6.1",
- "webpack-dev-server": "^3.2.1",
- "webpack-hot-middleware": "^2.24.3"
- },
- "dependencies": {
- "@fortawesome/fontawesome-free-solid": "^5.0.13",
- "@fortawesome/fontawesome-svg-core": "^1.2.15",
- "@hig/flyout": "^1.0.3",
- "@fortawesome/free-solid-svg-icons": "^5.7.2",
- "@fortawesome/react-fontawesome": "^0.1.4",
- "@types/bcrypt-nodejs": "0.0.30",
- "@types/bluebird": "^3.5.25",
- "@types/body-parser": "^1.17.0",
- "@types/express": "^4.16.1",
- "@types/express-flash": "0.0.0",
- "@types/express-session": "^1.15.12",
- "@types/express-validator": "^3.0.0",
- "@types/formidable": "^1.0.31",
- "@types/jquery": "^3.3.29",
- "@types/jsonwebtoken": "^8.3.2",
- "@types/lodash": "^4.14.121",
- "@types/mongodb": "^3.1.22",
- "@types/mongoose": "^5.3.21",
- "@types/node": "^10.12.30",
- "@types/passport": "^1.0.0",
- "@types/passport-local": "^1.0.33",
- "@types/prosemirror-commands": "^1.0.1",
- "@types/prosemirror-history": "^1.0.1",
- "@types/prosemirror-keymap": "^1.0.1",
- "@types/prosemirror-model": "^1.7.0",
- "@types/prosemirror-schema-basic": "^1.0.1",
- "@types/prosemirror-schema-list": "^1.0.1",
- "@types/prosemirror-state": "^1.2.3",
- "@types/prosemirror-transform": "^1.1.0",
- "@types/prosemirror-view": "^1.3.1",
- "@types/pug": "^2.0.4",
- "@types/react": "^16.8.7",
- "@types/react-color": "^2.14.1",
- "@types/react-measure": "^2.0.4",
- "@types/react-table": "^6.7.22",
- "@types/request": "^2.48.1",
- "@types/socket.io": "^2.1.2",
- "@types/socket.io-client": "^1.4.32",
- "@types/typescript": "^2.0.0",
- "@types/uuid": "^3.4.4",
- "@types/webpack": "^4.4.25",
- "babel-runtime": "^6.26.0",
- "bcrypt-nodejs": "0.0.3",
- "bluebird": "^3.5.3",
- "body-parser": "^1.18.3",
- "connect-mongo": "^2.0.3",
- "express": "^4.16.4",
- "express-flash": "0.0.2",
- "express-session": "^1.15.6",
- "express-validator": "^5.3.1",
- "expressjs": "^1.0.1",
- "flexlayout-react": "^0.3.3",
- "formidable": "^1.2.1",
- "golden-layout": "^1.5.9",
- "html-to-image": "^0.1.0",
- "i": "^0.3.6",
- "jsonwebtoken": "^8.5.0",
- "jsx-to-string": "^1.4.0",
- "lodash": "^4.17.11",
- "mobx": "^5.9.0",
- "mobx-react": "^5.3.5",
- "mobx-react-devtools": "^6.1.1",
- "mongodb": "^3.1.13",
- "mongoose": "^5.4.18",
- "node-sass": "^4.11.0",
- "nodemon": "^1.18.10",
- "normalize.css": "^8.0.1",
- "npm": "^6.9.0",
- "passport": "^0.4.0",
- "passport-local": "^1.0.0",
- "prosemirror-commands": "^1.0.7",
- "prosemirror-example-setup": "^1.0.1",
- "prosemirror-history": "^1.0.4",
- "prosemirror-keymap": "^1.0.1",
- "prosemirror-model": "^1.7.0",
- "prosemirror-schema-basic": "^1.0.0",
- "prosemirror-schema-list": "^1.0.2",
- "prosemirror-state": "^1.2.2",
- "prosemirror-transform": "^1.1.3",
- "prosemirror-view": "^1.8.3",
- "pug": "^2.0.3",
- "raw-loader": "^1.0.0",
- "react": "^16.8.4",
- "react-color": "^2.17.0",
- "react-dimensions": "^1.3.1",
- "react-dom": "^16.8.4",
- "react-golden-layout": "^1.0.6",
- "react-image-lightbox": "^5.1.0",
- "react-jsx-parser": "^1.14.1",
- "react-measure": "^2.2.4",
- "react-mosaic": "0.0.20",
- "react-pdf": "^4.0.2",
- "react-pdf-highlighter": "^2.1.2",
- "react-pdf-js": "^4.0.2",
- "react-split-pane": "^0.1.85",
- "react-table": "^6.9.2",
- "request": "^2.88.0",
- "socket.io": "^2.2.0",
- "socket.io-client": "^2.2.0",
- "url-loader": "^1.1.2",
- "uuid": "^3.3.2"
- }
+ "name": "dash",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "start": "ts-node-dev -- src/server/index.ts",
+ "debug": "ts-node-dev --inspect -- src/server/index.ts",
+ "build": "webpack --env production",
+ "test": "mocha -r ts-node/register test/**/*.ts",
+ "tsc": "tsc"
+ },
+ "devDependencies": {
+ "@types/chai": "^4.1.7",
+ "@types/mocha": "^5.2.6",
+ "@types/react-dom": "^16.8.2",
+ "@types/webpack-dev-middleware": "^2.0.2",
+ "@types/webpack-hot-middleware": "^2.16.4",
+ "awesome-typescript-loader": "^5.2.1",
+ "chai": "^4.2.0",
+ "copy-webpack-plugin": "^4.6.0",
+ "css-loader": "^2.1.1",
+ "file-loader": "^3.0.1",
+ "fork-ts-checker-webpack-plugin": "^1.0.2",
+ "mocha": "^5.2.0",
+ "sass-loader": "^7.1.0",
+ "scss-loader": "0.0.1",
+ "style-loader": "^0.23.1",
+ "ts-loader": "^5.3.3",
+ "ts-node": "^7.0.1",
+ "ts-node-dev": "^1.0.0-pre.32",
+ "tslint": "^5.15.0",
+ "tslint-loader": "^3.5.4",
+ "typescript": "^3.4.1",
+ "webpack": "^4.29.6",
+ "webpack-cli": "^3.2.3",
+ "webpack-dev-middleware": "^3.6.1",
+ "webpack-dev-server": "^3.3.1",
+ "webpack-hot-middleware": "^2.24.3"
+ },
+ "dependencies": {
+ "@fortawesome/fontawesome-free-solid": "^5.0.13",
+ "@fortawesome/fontawesome-svg-core": "^1.2.15",
+ "@fortawesome/free-solid-svg-icons": "^5.7.2",
+ "@fortawesome/react-fontawesome": "^0.1.4",
+ "@hig/flyout": "^1.0.3",
+ "@hig/theme-context": "^2.1.3",
+ "@hig/theme-data": "^2.3.3",
+ "@trendmicro/react-dropdown": "^1.3.0",
+ "@types/async": "^2.4.1",
+ "@types/bcrypt-nodejs": "0.0.30",
+ "@types/bluebird": "^3.5.25",
+ "@types/body-parser": "^1.17.0",
+ "@types/connect-flash": "0.0.34",
+ "@types/cookie-parser": "^1.4.1",
+ "@types/cookie-session": "^2.0.36",
+ "@types/d3-format": "^1.3.1",
+ "@types/express": "^4.16.1",
+ "@types/express-flash": "0.0.0",
+ "@types/express-session": "^1.15.12",
+ "@types/express-validator": "^3.0.0",
+ "@types/formidable": "^1.0.31",
+ "@types/jquery": "^3.3.29",
+ "@types/jsonwebtoken": "^8.3.2",
+ "@types/lodash": "^4.14.121",
+ "@types/mobile-detect": "^1.3.4",
+ "@types/mongodb": "^3.1.22",
+ "@types/mongoose": "^5.3.21",
+ "@types/node": "^10.12.30",
+ "@types/nodemailer": "^4.6.6",
+ "@types/passport": "^1.0.0",
+ "@types/passport-local": "^1.0.33",
+ "@types/prosemirror-commands": "^1.0.1",
+ "@types/prosemirror-history": "^1.0.1",
+ "@types/prosemirror-inputrules": "^1.0.2",
+ "@types/prosemirror-keymap": "^1.0.1",
+ "@types/prosemirror-menu": "^1.0.1",
+ "@types/prosemirror-model": "^1.7.0",
+ "@types/prosemirror-schema-basic": "^1.0.1",
+ "@types/prosemirror-schema-list": "^1.0.1",
+ "@types/prosemirror-state": "^1.2.3",
+ "@types/prosemirror-transform": "^1.1.0",
+ "@types/prosemirror-view": "^1.3.1",
+ "@types/pug": "^2.0.4",
+ "@types/react": "^16.8.7",
+ "@types/react-color": "^2.14.1",
+ "@types/react-measure": "^2.0.4",
+ "@types/react-table": "^6.7.22",
+ "@types/request": "^2.48.1",
+ "@types/request-promise": "^4.1.42",
+ "@types/socket.io": "^2.1.2",
+ "@types/socket.io-client": "^1.4.32",
+ "@types/typescript": "^2.0.0",
+ "@types/uuid": "^3.4.4",
+ "@types/webpack": "^4.4.25",
+ "async": "^2.6.2",
+ "babel-runtime": "^6.26.0",
+ "bcrypt-nodejs": "0.0.3",
+ "bluebird": "^3.5.3",
+ "body-parser": "^1.18.3",
+ "bootstrap": "^4.3.1",
+ "class-transformer": "^0.2.0",
+ "connect-flash": "^0.1.1",
+ "connect-mongo": "^2.0.3",
+ "cookie-parser": "^1.4.4",
+ "cookie-session": "^2.0.0-beta.3",
+ "crypto-browserify": "^3.11.0",
+ "d3-format": "^1.3.2",
+ "express": "^4.16.4",
+ "express-flash": "0.0.2",
+ "express-session": "^1.15.6",
+ "express-validator": "^5.3.1",
+ "expressjs": "^1.0.1",
+ "flexlayout-react": "^0.3.3",
+ "font-awesome": "^4.7.0",
+ "formidable": "^1.2.1",
+ "golden-layout": "^1.5.9",
+ "html-to-image": "^0.1.0",
+ "i": "^0.3.6",
+ "jsonwebtoken": "^8.5.0",
+ "jsx-to-string": "^1.4.0",
+ "lodash": "^4.17.11",
+ "mobile-detect": "^1.4.3",
+ "mobx": "^5.9.0",
+ "mobx-react": "^5.3.5",
+ "mobx-react-devtools": "^6.1.1",
+ "mongodb": "^3.1.13",
+ "mongoose": "^5.4.18",
+ "node-sass": "^4.12.0",
+ "nodemailer": "^5.1.1",
+ "nodemon": "^1.18.10",
+ "normalize.css": "^8.0.1",
+ "npm": "^6.9.0",
+ "passport": "^0.4.0",
+ "passport-local": "^1.0.0",
+ "prosemirror-commands": "^1.0.7",
+ "prosemirror-example-setup": "^1.0.1",
+ "prosemirror-history": "^1.0.4",
+ "prosemirror-keymap": "^1.0.1",
+ "prosemirror-model": "^1.7.0",
+ "prosemirror-schema-basic": "^1.0.0",
+ "prosemirror-schema-list": "^1.0.2",
+ "prosemirror-state": "^1.2.2",
+ "prosemirror-transform": "^1.1.3",
+ "prosemirror-view": "^1.8.3",
+ "pug": "^2.0.3",
+ "raw-loader": "^1.0.0",
+ "react": "^16.8.4",
+ "react-bootstrap": "^1.0.0-beta.5",
+ "react-bootstrap-dropdown-menu": "^1.1.15",
+ "react-color": "^2.17.0",
+ "react-dimensions": "^1.3.1",
+ "react-dom": "^16.8.4",
+ "react-golden-layout": "^1.0.6",
+ "react-image-lightbox": "^5.1.0",
+ "react-jsx-parser": "^1.15.0",
+ "react-measure": "^2.2.4",
+ "react-mosaic": "0.0.20",
+ "react-pdf": "^4.0.2",
+ "react-pdf-highlighter": "^2.1.2",
+ "react-pdf-js": "^4.0.2",
+ "react-simple-dropdown": "^3.2.3",
+ "react-split-pane": "^0.1.85",
+ "react-table": "^6.9.2",
+ "request": "^2.88.0",
+ "request-promise": "^4.2.4",
+ "serializr": "^1.5.1",
+ "socket.io": "^2.2.0",
+ "socket.io-client": "^2.2.0",
+ "solr-node": "^1.1.3",
+ "typescript-collections": "^1.3.2",
+ "url-loader": "^1.1.2",
+ "uuid": "^3.3.2",
+ "xoauth2": "^1.2.0"
+ }
}
diff --git a/solr/conf/lang/contractions_ca.txt b/solr/conf/lang/contractions_ca.txt
new file mode 100644
index 000000000..307a85f91
--- /dev/null
+++ b/solr/conf/lang/contractions_ca.txt
@@ -0,0 +1,8 @@
+# Set of Catalan contractions for ElisionFilter
+# TODO: load this as a resource from the analyzer and sync it in build.xml
+d
+l
+m
+n
+s
+t
diff --git a/solr/conf/lang/contractions_fr.txt b/solr/conf/lang/contractions_fr.txt
new file mode 100644
index 000000000..f1bba51b2
--- /dev/null
+++ b/solr/conf/lang/contractions_fr.txt
@@ -0,0 +1,15 @@
+# Set of French contractions for ElisionFilter
+# TODO: load this as a resource from the analyzer and sync it in build.xml
+l
+m
+t
+qu
+n
+s
+j
+d
+c
+jusqu
+quoiqu
+lorsqu
+puisqu
diff --git a/solr/conf/lang/contractions_ga.txt b/solr/conf/lang/contractions_ga.txt
new file mode 100644
index 000000000..9ebe7fa34
--- /dev/null
+++ b/solr/conf/lang/contractions_ga.txt
@@ -0,0 +1,5 @@
+# Set of Irish contractions for ElisionFilter
+# TODO: load this as a resource from the analyzer and sync it in build.xml
+d
+m
+b
diff --git a/solr/conf/lang/contractions_it.txt b/solr/conf/lang/contractions_it.txt
new file mode 100644
index 000000000..cac040953
--- /dev/null
+++ b/solr/conf/lang/contractions_it.txt
@@ -0,0 +1,23 @@
+# Set of Italian contractions for ElisionFilter
+# TODO: load this as a resource from the analyzer and sync it in build.xml
+c
+l
+all
+dall
+dell
+nell
+sull
+coll
+pell
+gl
+agl
+dagl
+degl
+negl
+sugl
+un
+m
+t
+s
+v
+d
diff --git a/solr/conf/lang/hyphenations_ga.txt b/solr/conf/lang/hyphenations_ga.txt
new file mode 100644
index 000000000..4d2642cc5
--- /dev/null
+++ b/solr/conf/lang/hyphenations_ga.txt
@@ -0,0 +1,5 @@
+# Set of Irish hyphenations for StopFilter
+# TODO: load this as a resource from the analyzer and sync it in build.xml
+h
+n
+t
diff --git a/solr/conf/lang/stemdict_nl.txt b/solr/conf/lang/stemdict_nl.txt
new file mode 100644
index 000000000..441072971
--- /dev/null
+++ b/solr/conf/lang/stemdict_nl.txt
@@ -0,0 +1,6 @@
+# Set of overrides for the dutch stemmer
+# TODO: load this as a resource from the analyzer and sync it in build.xml
+fiets fiets
+bromfiets bromfiets
+ei eier
+kind kinder
diff --git a/solr/conf/lang/stoptags_ja.txt b/solr/conf/lang/stoptags_ja.txt
new file mode 100644
index 000000000..71b750845
--- /dev/null
+++ b/solr/conf/lang/stoptags_ja.txt
@@ -0,0 +1,420 @@
+#
+# This file defines a Japanese stoptag set for JapanesePartOfSpeechStopFilter.
+#
+# Any token with a part-of-speech tag that exactly matches those defined in this
+# file are removed from the token stream.
+#
+# Set your own stoptags by uncommenting the lines below. Note that comments are
+# not allowed on the same line as a stoptag. See LUCENE-3745 for frequency lists,
+# etc. that can be useful for building you own stoptag set.
+#
+# The entire possible tagset is provided below for convenience.
+#
+#####
+# noun: unclassified nouns
+#名詞
+#
+# noun-common: Common nouns or nouns where the sub-classification is undefined
+#名詞-一般
+#
+# noun-proper: Proper nouns where the sub-classification is undefined
+#名詞-固有名詞
+#
+# noun-proper-misc: miscellaneous proper nouns
+#名詞-固有名詞-一般
+#
+# noun-proper-person: Personal names where the sub-classification is undefined
+#名詞-固有名詞-人名
+#
+# noun-proper-person-misc: names that cannot be divided into surname and
+# given name; foreign names; names where the surname or given name is unknown.
+# e.g. お市の方
+#名詞-固有名詞-人名-一般
+#
+# noun-proper-person-surname: Mainly Japanese surnames.
+# e.g. 山田
+#名詞-固有名詞-人名-姓
+#
+# noun-proper-person-given_name: Mainly Japanese given names.
+# e.g. 太郎
+#名詞-固有名詞-人名-名
+#
+# noun-proper-organization: Names representing organizations.
+# e.g. 通産省, NHK
+#名詞-固有名詞-組織
+#
+# noun-proper-place: Place names where the sub-classification is undefined
+#名詞-固有名詞-地域
+#
+# noun-proper-place-misc: Place names excluding countries.
+# e.g. アジア, バルセロナ, 京都
+#名詞-固有名詞-地域-一般
+#
+# noun-proper-place-country: Country names.
+# e.g. 日本, オーストラリア
+#名詞-固有名詞-地域-国
+#
+# noun-pronoun: Pronouns where the sub-classification is undefined
+#名詞-代名詞
+#
+# noun-pronoun-misc: miscellaneous pronouns:
+# e.g. それ, ここ, あいつ, あなた, あちこち, いくつ, どこか, なに, みなさん, みんな, わたくし, われわれ
+#名詞-代名詞-一般
+#
+# noun-pronoun-contraction: Spoken language contraction made by combining a
+# pronoun and the particle 'wa'.
+# e.g. ありゃ, こりゃ, こりゃあ, そりゃ, そりゃあ
+#名詞-代名詞-縮約
+#
+# noun-adverbial: Temporal nouns such as names of days or months that behave
+# like adverbs. Nouns that represent amount or ratios and can be used adverbially,
+# e.g. 金曜, 一月, 午後, 少量
+#名詞-副詞可能
+#
+# noun-verbal: Nouns that take arguments with case and can appear followed by
+# 'suru' and related verbs (する, できる, なさる, くださる)
+# e.g. インプット, 愛着, 悪化, 悪戦苦闘, 一安心, 下取り
+#名詞-サ変接続
+#
+# noun-adjective-base: The base form of adjectives, words that appear before な ("na")
+# e.g. 健康, 安易, 駄目, だめ
+#名詞-形容動詞語幹
+#
+# noun-numeric: Arabic numbers, Chinese numerals, and counters like 何 (回), 数.
+# e.g. 0, 1, 2, 何, 数, 幾
+#名詞-数
+#
+# noun-affix: noun affixes where the sub-classification is undefined
+#名詞-非自立
+#
+# noun-affix-misc: Of adnominalizers, the case-marker の ("no"), and words that
+# attach to the base form of inflectional words, words that cannot be classified
+# into any of the other categories below. This category includes indefinite nouns.
+# e.g. あかつき, 暁, かい, 甲斐, 気, きらい, 嫌い, くせ, 癖, こと, 事, ごと, 毎, しだい, 次第,
+# 順, せい, 所為, ついで, 序で, つもり, 積もり, 点, どころ, の, はず, 筈, はずみ, 弾み,
+# 拍子, ふう, ふり, 振り, ほう, 方, 旨, もの, 物, 者, ゆえ, 故, ゆえん, 所以, わけ, 訳,
+# わり, 割り, 割, ん-口語/, もん-口語/
+#名詞-非自立-一般
+#
+# noun-affix-adverbial: noun affixes that that can behave as adverbs.
+# e.g. あいだ, 間, あげく, 挙げ句, あと, 後, 余り, 以外, 以降, 以後, 以上, 以前, 一方, うえ,
+# 上, うち, 内, おり, 折り, かぎり, 限り, きり, っきり, 結果, ころ, 頃, さい, 際, 最中, さなか,
+# 最中, じたい, 自体, たび, 度, ため, 為, つど, 都度, とおり, 通り, とき, 時, ところ, 所,
+# とたん, 途端, なか, 中, のち, 後, ばあい, 場合, 日, ぶん, 分, ほか, 他, まえ, 前, まま,
+# 儘, 侭, みぎり, 矢先
+#名詞-非自立-副詞可能
+#
+# noun-affix-aux: noun affixes treated as 助動詞 ("auxiliary verb") in school grammars
+# with the stem よう(だ) ("you(da)").
+# e.g. よう, やう, 様 (よう)
+#名詞-非自立-助動詞語幹
+#
+# noun-affix-adjective-base: noun affixes that can connect to the indeclinable
+# connection form な (aux "da").
+# e.g. みたい, ふう
+#名詞-非自立-形容動詞語幹
+#
+# noun-special: special nouns where the sub-classification is undefined.
+#名詞-特殊
+#
+# noun-special-aux: The そうだ ("souda") stem form that is used for reporting news, is
+# treated as 助動詞 ("auxiliary verb") in school grammars, and attach to the base
+# form of inflectional words.
+# e.g. そう
+#名詞-特殊-助動詞語幹
+#
+# noun-suffix: noun suffixes where the sub-classification is undefined.
+#名詞-接尾
+#
+# noun-suffix-misc: Of the nouns or stem forms of other parts of speech that connect
+# to ガル or タイ and can combine into compound nouns, words that cannot be classified into
+# any of the other categories below. In general, this category is more inclusive than
+# 接尾語 ("suffix") and is usually the last element in a compound noun.
+# e.g. おき, かた, 方, 甲斐 (がい), がかり, ぎみ, 気味, ぐるみ, (~した) さ, 次第, 済 (ず) み,
+# よう, (でき)っこ, 感, 観, 性, 学, 類, 面, 用
+#名詞-接尾-一般
+#
+# noun-suffix-person: Suffixes that form nouns and attach to person names more often
+# than other nouns.
+# e.g. 君, 様, 著
+#名詞-接尾-人名
+#
+# noun-suffix-place: Suffixes that form nouns and attach to place names more often
+# than other nouns.
+# e.g. 町, 市, 県
+#名詞-接尾-地域
+#
+# noun-suffix-verbal: Of the suffixes that attach to nouns and form nouns, those that
+# can appear before スル ("suru").
+# e.g. 化, 視, 分け, 入り, 落ち, 買い
+#名詞-接尾-サ変接続
+#
+# noun-suffix-aux: The stem form of そうだ (様態) that is used to indicate conditions,
+# is treated as 助動詞 ("auxiliary verb") in school grammars, and attach to the
+# conjunctive form of inflectional words.
+# e.g. そう
+#名詞-接尾-助動詞語幹
+#
+# noun-suffix-adjective-base: Suffixes that attach to other nouns or the conjunctive
+# form of inflectional words and appear before the copula だ ("da").
+# e.g. 的, げ, がち
+#名詞-接尾-形容動詞語幹
+#
+# noun-suffix-adverbial: Suffixes that attach to other nouns and can behave as adverbs.
+# e.g. 後 (ご), 以後, 以降, 以前, 前後, 中, 末, 上, 時 (じ)
+#名詞-接尾-副詞可能
+#
+# noun-suffix-classifier: Suffixes that attach to numbers and form nouns. This category
+# is more inclusive than 助数詞 ("classifier") and includes common nouns that attach
+# to numbers.
+# e.g. 個, つ, 本, 冊, パーセント, cm, kg, カ月, か国, 区画, 時間, 時半
+#名詞-接尾-助数詞
+#
+# noun-suffix-special: Special suffixes that mainly attach to inflecting words.
+# e.g. (楽し) さ, (考え) 方
+#名詞-接尾-特殊
+#
+# noun-suffix-conjunctive: Nouns that behave like conjunctions and join two words
+# together.
+# e.g. (日本) 対 (アメリカ), 対 (アメリカ), (3) 対 (5), (女優) 兼 (主婦)
+#名詞-接続詞的
+#
+# noun-verbal_aux: Nouns that attach to the conjunctive particle て ("te") and are
+# semantically verb-like.
+# e.g. ごらん, ご覧, 御覧, 頂戴
+#名詞-動詞非自立的
+#
+# noun-quotation: text that cannot be segmented into words, proverbs, Chinese poetry,
+# dialects, English, etc. Currently, the only entry for 名詞 引用文字列 ("noun quotation")
+# is いわく ("iwaku").
+#名詞-引用文字列
+#
+# noun-nai_adjective: Words that appear before the auxiliary verb ない ("nai") and
+# behave like an adjective.
+# e.g. 申し訳, 仕方, とんでも, 違い
+#名詞-ナイ形容詞語幹
+#
+#####
+# prefix: unclassified prefixes
+#接頭詞
+#
+# prefix-nominal: Prefixes that attach to nouns (including adjective stem forms)
+# excluding numerical expressions.
+# e.g. お (水), 某 (氏), 同 (社), 故 (~氏), 高 (品質), お (見事), ご (立派)
+#接頭詞-名詞接続
+#
+# prefix-verbal: Prefixes that attach to the imperative form of a verb or a verb
+# in conjunctive form followed by なる/なさる/くださる.
+# e.g. お (読みなさい), お (座り)
+#接頭詞-動詞接続
+#
+# prefix-adjectival: Prefixes that attach to adjectives.
+# e.g. お (寒いですねえ), バカ (でかい)
+#接頭詞-形容詞接続
+#
+# prefix-numerical: Prefixes that attach to numerical expressions.
+# e.g. 約, およそ, 毎時
+#接頭詞-数接続
+#
+#####
+# verb: unclassified verbs
+#動詞
+#
+# verb-main:
+#動詞-自立
+#
+# verb-auxiliary:
+#動詞-非自立
+#
+# verb-suffix:
+#動詞-接尾
+#
+#####
+# adjective: unclassified adjectives
+#形容詞
+#
+# adjective-main:
+#形容詞-自立
+#
+# adjective-auxiliary:
+#形容詞-非自立
+#
+# adjective-suffix:
+#形容詞-接尾
+#
+#####
+# adverb: unclassified adverbs
+#副詞
+#
+# adverb-misc: Words that can be segmented into one unit and where adnominal
+# modification is not possible.
+# e.g. あいかわらず, 多分
+#副詞-一般
+#
+# adverb-particle_conjunction: Adverbs that can be followed by の, は, に,
+# な, する, だ, etc.
+# e.g. こんなに, そんなに, あんなに, なにか, なんでも
+#副詞-助詞類接続
+#
+#####
+# adnominal: Words that only have noun-modifying forms.
+# e.g. この, その, あの, どの, いわゆる, なんらかの, 何らかの, いろんな, こういう, そういう, ああいう,
+# どういう, こんな, そんな, あんな, どんな, 大きな, 小さな, おかしな, ほんの, たいした,
+# 「(, も) さる (ことながら)」, 微々たる, 堂々たる, 単なる, いかなる, 我が」「同じ, 亡き
+#連体詞
+#
+#####
+# conjunction: Conjunctions that can occur independently.
+# e.g. が, けれども, そして, じゃあ, それどころか
+接続詞
+#
+#####
+# particle: unclassified particles.
+助詞
+#
+# particle-case: case particles where the subclassification is undefined.
+助詞-格助詞
+#
+# particle-case-misc: Case particles.
+# e.g. から, が, で, と, に, へ, より, を, の, にて
+助詞-格助詞-一般
+#
+# particle-case-quote: the "to" that appears after nouns, a person’s speech,
+# quotation marks, expressions of decisions from a meeting, reasons, judgements,
+# conjectures, etc.
+# e.g. ( だ) と (述べた.), ( である) と (して執行猶予...)
+助詞-格助詞-引用
+#
+# particle-case-compound: Compounds of particles and verbs that mainly behave
+# like case particles.
+# e.g. という, といった, とかいう, として, とともに, と共に, でもって, にあたって, に当たって, に当って,
+# にあたり, に当たり, に当り, に当たる, にあたる, において, に於いて,に於て, における, に於ける,
+# にかけ, にかけて, にかんし, に関し, にかんして, に関して, にかんする, に関する, に際し,
+# に際して, にしたがい, に従い, に従う, にしたがって, に従って, にたいし, に対し, にたいして,
+# に対して, にたいする, に対する, について, につき, につけ, につけて, につれ, につれて, にとって,
+# にとり, にまつわる, によって, に依って, に因って, により, に依り, に因り, による, に依る, に因る,
+# にわたって, にわたる, をもって, を以って, を通じ, を通じて, を通して, をめぐって, をめぐり, をめぐる,
+# って-口語/, ちゅう-関西弁「という」/, (何) ていう (人)-口語/, っていう-口語/, といふ, とかいふ
+助詞-格助詞-連語
+#
+# particle-conjunctive:
+# e.g. から, からには, が, けれど, けれども, けど, し, つつ, て, で, と, ところが, どころか, とも, ども,
+# ながら, なり, ので, のに, ば, ものの, や ( した), やいなや, (ころん) じゃ(いけない)-口語/,
+# (行っ) ちゃ(いけない)-口語/, (言っ) たって (しかたがない)-口語/, (それがなく)ったって (平気)-口語/
+助詞-接続助詞
+#
+# particle-dependency:
+# e.g. こそ, さえ, しか, すら, は, も, ぞ
+助詞-係助詞
+#
+# particle-adverbial:
+# e.g. がてら, かも, くらい, 位, ぐらい, しも, (学校) じゃ(これが流行っている)-口語/,
+# (それ)じゃあ (よくない)-口語/, ずつ, (私) なぞ, など, (私) なり (に), (先生) なんか (大嫌い)-口語/,
+# (私) なんぞ, (先生) なんて (大嫌い)-口語/, のみ, だけ, (私) だって-口語/, だに,
+# (彼)ったら-口語/, (お茶) でも (いかが), 等 (とう), (今後) とも, ばかり, ばっか-口語/, ばっかり-口語/,
+# ほど, 程, まで, 迄, (誰) も (が)([助詞-格助詞] および [助詞-係助詞] の前に位置する「も」)
+助詞-副助詞
+#
+# particle-interjective: particles with interjective grammatical roles.
+# e.g. (松島) や
+助詞-間投助詞
+#
+# particle-coordinate:
+# e.g. と, たり, だの, だり, とか, なり, や, やら
+助詞-並立助詞
+#
+# particle-final:
+# e.g. かい, かしら, さ, ぜ, (だ)っけ-口語/, (とまってる) で-方言/, な, ナ, なあ-口語/, ぞ, ね, ネ,
+# ねぇ-口語/, ねえ-口語/, ねん-方言/, の, のう-口語/, や, よ, ヨ, よぉ-口語/, わ, わい-口語/
+助詞-終助詞
+#
+# particle-adverbial/conjunctive/final: The particle "ka" when unknown whether it is
+# adverbial, conjunctive, or sentence final. For example:
+# (a) 「A か B か」. Ex:「(国内で運用する) か,(海外で運用する) か (.)」
+# (b) Inside an adverb phrase. Ex:「(幸いという) か (, 死者はいなかった.)」
+# 「(祈りが届いたせい) か (, 試験に合格した.)」
+# (c) 「かのように」. Ex:「(何もなかった) か (のように振る舞った.)」
+# e.g. か
+助詞-副助詞/並立助詞/終助詞
+#
+# particle-adnominalizer: The "no" that attaches to nouns and modifies
+# non-inflectional words.
+助詞-連体化
+#
+# particle-adnominalizer: The "ni" and "to" that appear following nouns and adverbs
+# that are giongo, giseigo, or gitaigo.
+# e.g. に, と
+助詞-副詞化
+#
+# particle-special: A particle that does not fit into one of the above classifications.
+# This includes particles that are used in Tanka, Haiku, and other poetry.
+# e.g. かな, けむ, ( しただろう) に, (あんた) にゃ(わからん), (俺) ん (家)
+助詞-特殊
+#
+#####
+# auxiliary-verb:
+助動詞
+#
+#####
+# interjection: Greetings and other exclamations.
+# e.g. おはよう, おはようございます, こんにちは, こんばんは, ありがとう, どうもありがとう, ありがとうございます,
+# いただきます, ごちそうさま, さよなら, さようなら, はい, いいえ, ごめん, ごめんなさい
+#感動詞
+#
+#####
+# symbol: unclassified Symbols.
+記号
+#
+# symbol-misc: A general symbol not in one of the categories below.
+# e.g. [○◎@$〒→+]
+記号-一般
+#
+# symbol-comma: Commas
+# e.g. [,、]
+記号-読点
+#
+# symbol-period: Periods and full stops.
+# e.g. [..。]
+記号-句点
+#
+# symbol-space: Full-width whitespace.
+記号-空白
+#
+# symbol-open_bracket:
+# e.g. [({‘“『【]
+記号-括弧開
+#
+# symbol-close_bracket:
+# e.g. [)}’”』」】]
+記号-括弧閉
+#
+# symbol-alphabetic:
+#記号-アルファベット
+#
+#####
+# other: unclassified other
+#その他
+#
+# other-interjection: Words that are hard to classify as noun-suffixes or
+# sentence-final particles.
+# e.g. (だ)ァ
+その他-間投
+#
+#####
+# filler: Aizuchi that occurs during a conversation or sounds inserted as filler.
+# e.g. あの, うんと, えと
+フィラー
+#
+#####
+# non-verbal: non-verbal sound.
+非言語音
+#
+#####
+# fragment:
+#語断片
+#
+#####
+# unknown: unknown part of speech.
+#未知語
+#
+##### End of file
diff --git a/solr/conf/lang/stopwords_ar.txt b/solr/conf/lang/stopwords_ar.txt
new file mode 100644
index 000000000..046829db6
--- /dev/null
+++ b/solr/conf/lang/stopwords_ar.txt
@@ -0,0 +1,125 @@
+# This file was created by Jacques Savoy and is distributed under the BSD license.
+# See http://members.unine.ch/jacques.savoy/clef/index.html.
+# Also see http://www.opensource.org/licenses/bsd-license.html
+# Cleaned on October 11, 2009 (not normalized, so use before normalization)
+# This means that when modifying this list, you might need to add some
+# redundant entries, for example containing forms with both أ and ا
+من
+ومن
+منها
+منه
+في
+وفي
+فيها
+فيه
+ثم
+او
+أو
+بها
+به
+اى
+اي
+أي
+أى
+لا
+ولا
+الا
+ألا
+إلا
+لكن
+ما
+وما
+كما
+فما
+عن
+مع
+اذا
+إذا
+ان
+أن
+إن
+انها
+أنها
+إنها
+انه
+أنه
+إنه
+بان
+بأن
+فان
+فأن
+وان
+وأن
+وإن
+التى
+التي
+الذى
+الذي
+الذين
+الى
+الي
+إلى
+إلي
+على
+عليها
+عليه
+اما
+أما
+إما
+ايضا
+أيضا
+كل
+وكل
+لم
+ولم
+لن
+ولن
+هى
+هي
+هو
+وهى
+وهي
+وهو
+فهى
+فهي
+فهو
+انت
+أنت
+لك
+لها
+له
+هذه
+هذا
+تلك
+ذلك
+هناك
+كانت
+كان
+يكون
+تكون
+وكانت
+وكان
+غير
+بعض
+قد
+نحو
+بين
+بينما
+منذ
+ضمن
+حيث
+الان
+الآن
+خلال
+بعد
+قبل
+حتى
+عند
+عندما
+لدى
+جميع
diff --git a/solr/conf/lang/stopwords_bg.txt b/solr/conf/lang/stopwords_bg.txt
new file mode 100644
index 000000000..1ae4ba2ae
--- /dev/null
+++ b/solr/conf/lang/stopwords_bg.txt
@@ -0,0 +1,193 @@
+# This file was created by Jacques Savoy and is distributed under the BSD license.
+# See http://members.unine.ch/jacques.savoy/clef/index.html.
+# Also see http://www.opensource.org/licenses/bsd-license.html
+аз
+ако
+ала
+бе
+без
+беше
+би
+бил
+била
+били
+било
+близо
+бъдат
+бъде
+бяха
+вас
+ваш
+ваша
+вероятно
+вече
+взема
+ви
+вие
+винаги
+все
+всеки
+всички
+всичко
+всяка
+във
+въпреки
+върху
+ги
+главно
+го
+да
+дали
+до
+докато
+докога
+дори
+досега
+доста
+едва
+един
+ето
+за
+зад
+заедно
+заради
+засега
+затова
+защо
+защото
+из
+или
+им
+има
+имат
+иска
+каза
+как
+каква
+какво
+както
+какъв
+като
+кога
+когато
+което
+които
+кой
+който
+колко
+която
+къде
+където
+към
+ли
+ме
+между
+мен
+ми
+мнозина
+мога
+могат
+може
+моля
+момента
+му
+на
+над
+назад
+най
+направи
+напред
+например
+нас
+не
+него
+нея
+ни
+ние
+никой
+нито
+но
+някои
+някой
+няма
+обаче
+около
+освен
+особено
+от
+отгоре
+отново
+още
+пак
+по
+повече
+повечето
+под
+поне
+поради
+после
+почти
+прави
+пред
+преди
+през
+при
+пък
+първо
+са
+само
+се
+сега
+си
+скоро
+след
+сме
+според
+сред
+срещу
+сте
+съм
+със
+също
+тази
+така
+такива
+такъв
+там
+твой
+те
+тези
+ти
+тн
+то
+това
+тогава
+този
+той
+толкова
+точно
+трябва
+тук
+тъй
+тя
+тях
+харесва
+че
+често
+чрез
+ще
+щом
diff --git a/solr/conf/lang/stopwords_ca.txt b/solr/conf/lang/stopwords_ca.txt
new file mode 100644
index 000000000..3da65deaf
--- /dev/null
+++ b/solr/conf/lang/stopwords_ca.txt
@@ -0,0 +1,220 @@
+# Catalan stopwords from http://github.com/vcl/cue.language (Apache 2 Licensed)
+a
+abans
+ací
+ah
+així
+això
+al
+als
+aleshores
+algun
+alguna
+algunes
+alguns
+alhora
+allà
+allí
+allò
+altra
+altre
+altres
+amb
+ambdós
+ambdues
+apa
+aquell
+aquella
+aquelles
+aquells
+aquest
+aquesta
+aquestes
+aquests
+aquí
+baix
+cada
+cadascú
+cadascuna
+cadascunes
+cadascuns
+com
+contra
+d'un
+d'una
+d'unes
+d'uns
+dalt
+de
+del
+dels
+des
+després
+dins
+dintre
+donat
+doncs
+durant
+e
+eh
+el
+els
+em
+en
+encara
+ens
+entre
+érem
+eren
+éreu
+es
+és
+esta
+està
+estàvem
+estaven
+estàveu
+esteu
+et
+etc
+ets
+fins
+fora
+gairebé
+ha
+han
+has
+havia
+he
+hem
+heu
+hi
+ho
+i
+igual
+iguals
+ja
+l'hi
+la
+les
+li
+li'n
+llavors
+m'he
+ma
+mal
+malgrat
+mateix
+mateixa
+mateixes
+mateixos
+me
+mentre
+més
+meu
+meus
+meva
+meves
+molt
+molta
+moltes
+molts
+mon
+mons
+n'he
+n'hi
+ne
+ni
+no
+nogensmenys
+només
+nosaltres
+nostra
+nostre
+nostres
+o
+oh
+oi
+on
+pas
+pel
+pels
+per
+però
+perquè
+poc
+poca
+pocs
+poques
+potser
+propi
+qual
+quals
+quan
+quant
+que
+què
+quelcom
+qui
+quin
+quina
+quines
+quins
+s'ha
+s'han
+sa
+semblant
+semblants
+ses
+seu
+seus
+seva
+seva
+seves
+si
+sobre
+sobretot
+sóc
+solament
+sols
+son
+són
+sons
+sota
+sou
+t'ha
+t'han
+t'he
+ta
+tal
+també
+tampoc
+tan
+tant
+tanta
+tantes
+teu
+teus
+teva
+teves
+ton
+tons
+tot
+tota
+totes
+tots
+un
+una
+unes
+uns
+us
+va
+vaig
+vam
+van
+vas
+veu
+vosaltres
+vostra
+vostre
+vostres
diff --git a/solr/conf/lang/stopwords_cz.txt b/solr/conf/lang/stopwords_cz.txt
new file mode 100644
index 000000000..53c6097da
--- /dev/null
+++ b/solr/conf/lang/stopwords_cz.txt
@@ -0,0 +1,172 @@
+a
+s
+k
+o
+i
+u
+v
+z
+dnes
+cz
+tímto
+budeš
+budem
+byli
+jseš
+můj
+svým
+ta
+tomto
+tohle
+tuto
+tyto
+jej
+zda
+proč
+máte
+tato
+kam
+tohoto
+kdo
+kteří
+mi
+nám
+tom
+tomuto
+mít
+nic
+proto
+kterou
+byla
+toho
+protože
+asi
+ho
+naši
+napište
+re
+což
+tím
+takže
+svých
+její
+svými
+jste
+aj
+tu
+tedy
+teto
+bylo
+kde
+ke
+pravé
+ji
+nad
+nejsou
+či
+pod
+téma
+mezi
+přes
+ty
+pak
+vám
+ani
+když
+však
+neg
+jsem
+tento
+článku
+články
+aby
+jsme
+před
+pta
+jejich
+byl
+ještě
+až
+bez
+také
+pouze
+první
+vaše
+která
+nás
+nový
+tipy
+pokud
+může
+strana
+jeho
+své
+jiné
+zprávy
+nové
+není
+vás
+jen
+podle
+zde
+už
+být
+více
+bude
+již
+než
+který
+by
+které
+co
+nebo
+ten
+tak
+má
+při
+od
+po
+jsou
+jak
+další
+ale
+si
+se
+ve
+to
+jako
+za
+zpět
+ze
+do
+pro
+je
+na
+atd
+atp
+jakmile
+přičemž
+já
+on
+ona
+ono
+oni
+ony
+my
+vy
+jí
+ji
+mě
+mne
+jemu
+tomu
+těm
+těmu
+němu
+němuž
+jehož
+jíž
+jelikož
+jež
+jakož
+načež
diff --git a/solr/conf/lang/stopwords_da.txt b/solr/conf/lang/stopwords_da.txt
new file mode 100644
index 000000000..42e6145b9
--- /dev/null
+++ b/solr/conf/lang/stopwords_da.txt
@@ -0,0 +1,110 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/danish/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | A Danish stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+ | This is a ranked list (commonest to rarest) of stopwords derived from
+ | a large text sample.
+
+
+og | and
+i | in
+jeg | I
+det | that (dem. pronoun)/it (pers. pronoun)
+at | that (in front of a sentence)/to (with infinitive)
+en | a/an
+den | it (pers. pronoun)/that (dem. pronoun)
+til | to/at/for/until/against/by/of/into, more
+er | present tense of "to be"
+som | who, as
+på | on/upon/in/on/at/to/after/of/with/for, on
+de | they
+med | with/by/in, along
+han | he
+af | of/by/from/off/for/in/with/on, off
+for | at/for/to/from/by/of/ago, in front/before, because
+ikke | not
+der | who/which, there/those
+var | past tense of "to be"
+mig | me/myself
+sig | oneself/himself/herself/itself/themselves
+men | but
+et | a/an/one, one (number), someone/somebody/one
+har | present tense of "to have"
+om | round/about/for/in/a, about/around/down, if
+vi | we
+min | my
+havde | past tense of "to have"
+ham | him
+hun | she
+nu | now
+over | over/above/across/by/beyond/past/on/about, over/past
+da | then, when/as/since
+fra | from/off/since, off, since
+du | you
+ud | out
+sin | his/her/its/one's
+dem | them
+os | us/ourselves
+op | up
+man | you/one
+hans | his
+hvor | where
+eller | or
+hvad | what
+skal | must/shall etc.
+selv | myself/youself/herself/ourselves etc., even
+her | here
+alle | all/everyone/everybody etc.
+vil | will (verb)
+blev | past tense of "to stay/to remain/to get/to become"
+kunne | could
+ind | in
+når | when
+være | present tense of "to be"
+dog | however/yet/after all
+noget | something
+ville | would
+jo | you know/you see (adv), yes
+deres | their/theirs
+efter | after/behind/according to/for/by/from, later/afterwards
+ned | down
+skulle | should
+denne | this
+end | than
+dette | this
+mit | my/mine
+også | also
+under | under/beneath/below/during, below/underneath
+have | have
+dig | you
+anden | other
+hende | her
+mine | my
+alt | everything
+meget | much/very, plenty of
+sit | his, her, its, one's
+sine | his, her, its, one's
+vor | our
+mod | against
+disse | these
+hvis | if
+din | your/yours
+nogle | some
+hos | by/at
+blive | be/become
+mange | many
+ad | by/through
+bliver | present tense of "to be/to become"
+hendes | her/hers
+været | be
+thi | for (conj)
+jer | you
+sådan | such, like this/like that
diff --git a/solr/conf/lang/stopwords_de.txt b/solr/conf/lang/stopwords_de.txt
new file mode 100644
index 000000000..86525e7ae
--- /dev/null
+++ b/solr/conf/lang/stopwords_de.txt
@@ -0,0 +1,294 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/german/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | A German stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+ | The number of forms in this list is reduced significantly by passing it
+ | through the German stemmer.
+
+
+aber | but
+
+alle | all
+allem
+allen
+aller
+alles
+
+als | than, as
+also | so
+am | an + dem
+an | at
+
+ander | other
+andere
+anderem
+anderen
+anderer
+anderes
+anderm
+andern
+anderr
+anders
+
+auch | also
+auf | on
+aus | out of
+bei | by
+bin | am
+bis | until
+bist | art
+da | there
+damit | with it
+dann | then
+
+der | the
+den
+des
+dem
+die
+das
+
+daß | that
+
+derselbe | the same
+derselben
+denselben
+desselben
+demselben
+dieselbe
+dieselben
+dasselbe
+
+dazu | to that
+
+dein | thy
+deine
+deinem
+deinen
+deiner
+deines
+
+denn | because
+
+derer | of those
+dessen | of him
+
+dich | thee
+dir | to thee
+du | thou
+
+dies | this
+diese
+diesem
+diesen
+dieser
+dieses
+
+
+doch | (several meanings)
+dort | (over) there
+
+
+durch | through
+
+ein | a
+eine
+einem
+einen
+einer
+eines
+
+einig | some
+einige
+einigem
+einigen
+einiger
+einiges
+
+einmal | once
+
+er | he
+ihn | him
+ihm | to him
+
+es | it
+etwas | something
+
+euer | your
+eure
+eurem
+euren
+eurer
+eures
+
+für | for
+gegen | towards
+gewesen | p.p. of sein
+hab | have
+habe | have
+haben | have
+hat | has
+hatte | had
+hatten | had
+hier | here
+hin | there
+hinter | behind
+
+ich | I
+mich | me
+mir | to me
+
+
+ihr | you, to her
+ihre
+ihrem
+ihren
+ihrer
+ihres
+euch | to you
+
+im | in + dem
+in | in
+indem | while
+ins | in + das
+ist | is
+
+jede | each, every
+jedem
+jeden
+jeder
+jedes
+
+jene | that
+jenem
+jenen
+jener
+jenes
+
+jetzt | now
+kann | can
+
+kein | no
+keine
+keinem
+keinen
+keiner
+keines
+
+können | can
+könnte | could
+machen | do
+man | one
+
+manche | some, many a
+manchem
+manchen
+mancher
+manches
+
+mein | my
+meine
+meinem
+meinen
+meiner
+meines
+
+mit | with
+muss | must
+musste | had to
+nach | to(wards)
+nicht | not
+nichts | nothing
+noch | still, yet
+nun | now
+nur | only
+ob | whether
+oder | or
+ohne | without
+sehr | very
+
+sein | his
+seine
+seinem
+seinen
+seiner
+seines
+
+selbst | self
+sich | herself
+
+sie | they, she
+ihnen | to them
+
+sind | are
+so | so
+
+solche | such
+solchem
+solchen
+solcher
+solches
+
+soll | shall
+sollte | should
+sondern | but
+sonst | else
+über | over
+um | about, around
+und | and
+
+uns | us
+unse
+unsem
+unsen
+unser
+unses
+
+unter | under
+viel | much
+vom | von + dem
+von | from
+vor | before
+während | while
+war | was
+waren | were
+warst | wast
+was | what
+weg | away, off
+weil | because
+weiter | further
+
+welche | which
+welchem
+welchen
+welcher
+welches
+
+wenn | when
+werde | will
+werden | will
+wie | how
+wieder | again
+will | want
+wir | we
+wird | will
+wirst | willst
+wo | where
+wollen | want
+wollte | wanted
+würde | would
+würden | would
+zu | to
+zum | zu + dem
+zur | zu + der
+zwar | indeed
+zwischen | between
+
diff --git a/solr/conf/lang/stopwords_el.txt b/solr/conf/lang/stopwords_el.txt
new file mode 100644
index 000000000..232681f5b
--- /dev/null
+++ b/solr/conf/lang/stopwords_el.txt
@@ -0,0 +1,78 @@
+# Lucene Greek Stopwords list
+# Note: by default this file is used after GreekLowerCaseFilter,
+# so when modifying this file use 'σ' instead of 'ς'
+ο
+το
+οι
+τα
+του
+τησ
+των
+τον
+την
+και
+κι
+ειμαι
+εισαι
+ειναι
+ειμαστε
+ειστε
+στο
+στον
+στη
+στην
+μα
+αλλα
+απο
+για
+προσ
+με
+σε
+ωσ
+παρα
+αντι
+κατα
+μετα
+θα
+να
+δε
+δεν
+μη
+μην
+επι
+ενω
+εαν
+αν
+τοτε
+που
+πωσ
+ποιοσ
+ποια
+ποιο
+ποιοι
+ποιεσ
+ποιων
+ποιουσ
+αυτοσ
+αυτη
+αυτο
+αυτοι
+αυτων
+αυτουσ
+αυτεσ
+αυτα
+εκεινοσ
+εκεινη
+εκεινο
+εκεινοι
+εκεινεσ
+εκεινα
+εκεινων
+εκεινουσ
+οπωσ
+ομωσ
+ισωσ
+οσο
+οτι
diff --git a/solr/conf/lang/stopwords_en.txt b/solr/conf/lang/stopwords_en.txt
new file mode 100644
index 000000000..2c164c0b2
--- /dev/null
+++ b/solr/conf/lang/stopwords_en.txt
@@ -0,0 +1,54 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# a couple of test stopwords to test that the words are really being
+# configured from this file:
+stopworda
+stopwordb
+
+# Standard english stop words taken from Lucene's StopAnalyzer
+a
+an
+and
+are
+as
+at
+be
+but
+by
+for
+if
+in
+into
+is
+it
+no
+not
+of
+on
+or
+such
+that
+the
+their
+then
+there
+these
+they
+this
+to
+was
+will
+with
diff --git a/solr/conf/lang/stopwords_es.txt b/solr/conf/lang/stopwords_es.txt
new file mode 100644
index 000000000..487d78c8d
--- /dev/null
+++ b/solr/conf/lang/stopwords_es.txt
@@ -0,0 +1,356 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/spanish/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | A Spanish stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+
+ | The following is a ranked list (commonest to rarest) of stopwords
+ | deriving from a large sample of text.
+
+ | Extra words have been added at the end.
+
+de | from, of
+la | the, her
+que | who, that
+el | the
+en | in
+y | and
+a | to
+los | the, them
+del | de + el
+se | himself, from him etc
+las | the, them
+por | for, by, etc
+un | a
+para | for
+con | with
+no | no
+una | a
+su | his, her
+al | a + el
+ | es from SER
+lo | him
+como | how
+más | more
+pero | pero
+sus | su plural
+le | to him, her
+ya | already
+o | or
+ | fue from SER
+este | this
+ | ha from HABER
+sí | himself etc
+porque | because
+esta | this
+ | son from SER
+entre | between
+ | está from ESTAR
+cuando | when
+muy | very
+sin | without
+sobre | on
+ | ser from SER
+ | tiene from TENER
+también | also
+me | me
+hasta | until
+hay | there is/are
+donde | where
+ | han from HABER
+quien | whom, that
+ | están from ESTAR
+ | estado from ESTAR
+desde | from
+todo | all
+nos | us
+durante | during
+ | estados from ESTAR
+todos | all
+uno | a
+les | to them
+ni | nor
+contra | against
+otros | other
+ | fueron from SER
+ese | that
+eso | that
+ | había from HABER
+ante | before
+ellos | they
+e | and (variant of y)
+esto | this
+mí | me
+antes | before
+algunos | some
+qué | what?
+unos | a
+yo | I
+otro | other
+otras | other
+otra | other
+él | he
+tanto | so much, many
+esa | that
+estos | these
+mucho | much, many
+quienes | who
+nada | nothing
+muchos | many
+cual | who
+ | sea from SER
+poco | few
+ella | she
+estar | to be
+ | haber from HABER
+estas | these
+ | estaba from ESTAR
+ | estamos from ESTAR
+algunas | some
+algo | something
+nosotros | we
+
+ | other forms
+
+mi | me
+mis | mi plural
+tú | thou
+te | thee
+ti | thee
+tu | thy
+tus | tu plural
+ellas | they
+nosotras | we
+vosotros | you
+vosotras | you
+os | you
+mío | mine
+mía |
+míos |
+mías |
+tuyo | thine
+tuya |
+tuyos |
+tuyas |
+suyo | his, hers, theirs
+suya |
+suyos |
+suyas |
+nuestro | ours
+nuestra |
+nuestros |
+nuestras |
+vuestro | yours
+vuestra |
+vuestros |
+vuestras |
+esos | those
+esas | those
+
+ | forms of estar, to be (not including the infinitive):
+estoy
+estás
+está
+estamos
+estáis
+están
+esté
+estés
+estemos
+estéis
+estén
+estaré
+estarás
+estará
+estaremos
+estaréis
+estarán
+estaría
+estarías
+estaríamos
+estaríais
+estarían
+estaba
+estabas
+estábamos
+estabais
+estaban
+estuve
+estuviste
+estuvo
+estuvimos
+estuvisteis
+estuvieron
+estuviera
+estuvieras
+estuviéramos
+estuvierais
+estuvieran
+estuviese
+estuvieses
+estuviésemos
+estuvieseis
+estuviesen
+estando
+estado
+estada
+estados
+estadas
+estad
+
+ | forms of haber, to have (not including the infinitive):
+he
+has
+ha
+hemos
+habéis
+han
+haya
+hayas
+hayamos
+hayáis
+hayan
+habré
+habrás
+habrá
+habremos
+habréis
+habrán
+habría
+habrías
+habríamos
+habríais
+habrían
+había
+habías
+habíamos
+habíais
+habían
+hube
+hubiste
+hubo
+hubimos
+hubisteis
+hubieron
+hubiera
+hubieras
+hubiéramos
+hubierais
+hubieran
+hubiese
+hubieses
+hubiésemos
+hubieseis
+hubiesen
+habiendo
+habido
+habida
+habidos
+habidas
+
+ | forms of ser, to be (not including the infinitive):
+soy
+eres
+es
+somos
+sois
+son
+sea
+seas
+seamos
+seáis
+sean
+seré
+serás
+será
+seremos
+seréis
+serán
+sería
+serías
+seríamos
+seríais
+serían
+era
+eras
+éramos
+erais
+eran
+fui
+fuiste
+fue
+fuimos
+fuisteis
+fueron
+fuera
+fueras
+fuéramos
+fuerais
+fueran
+fuese
+fueses
+fuésemos
+fueseis
+fuesen
+siendo
+sido
+ | sed also means 'thirst'
+
+ | forms of tener, to have (not including the infinitive):
+tengo
+tienes
+tiene
+tenemos
+tenéis
+tienen
+tenga
+tengas
+tengamos
+tengáis
+tengan
+tendré
+tendrás
+tendrá
+tendremos
+tendréis
+tendrán
+tendría
+tendrías
+tendríamos
+tendríais
+tendrían
+tenía
+tenías
+teníamos
+teníais
+tenían
+tuve
+tuviste
+tuvo
+tuvimos
+tuvisteis
+tuvieron
+tuviera
+tuvieras
+tuviéramos
+tuvierais
+tuvieran
+tuviese
+tuvieses
+tuviésemos
+tuvieseis
+tuviesen
+teniendo
+tenido
+tenida
+tenidos
+tenidas
+tened
+
diff --git a/solr/conf/lang/stopwords_eu.txt b/solr/conf/lang/stopwords_eu.txt
new file mode 100644
index 000000000..25f1db934
--- /dev/null
+++ b/solr/conf/lang/stopwords_eu.txt
@@ -0,0 +1,99 @@
+# example set of basque stopwords
+al
+anitz
+arabera
+asko
+baina
+bat
+batean
+batek
+bati
+batzuei
+batzuek
+batzuetan
+batzuk
+bera
+beraiek
+berau
+berauek
+bere
+berori
+beroriek
+beste
+bezala
+da
+dago
+dira
+ditu
+du
+dute
+edo
+egin
+ere
+eta
+eurak
+ez
+gainera
+gu
+gutxi
+guzti
+haiei
+haiek
+haietan
+hainbeste
+hala
+han
+handik
+hango
+hara
+hari
+hark
+hartan
+hau
+hauei
+hauek
+hauetan
+hemen
+hemendik
+hemengo
+hi
+hona
+honek
+honela
+honetan
+honi
+hor
+hori
+horiei
+horiek
+horietan
+horko
+horra
+horrek
+horrela
+horretan
+horri
+hortik
+hura
+izan
+ni
+noiz
+nola
+non
+nondik
+nongo
+nor
+nora
+ze
+zein
+zen
+zenbait
+zenbat
+zer
+zergatik
+ziren
+zituen
+zu
+zuek
+zuen
+zuten
diff --git a/solr/conf/lang/stopwords_fa.txt b/solr/conf/lang/stopwords_fa.txt
new file mode 100644
index 000000000..723641c6d
--- /dev/null
+++ b/solr/conf/lang/stopwords_fa.txt
@@ -0,0 +1,313 @@
+# This file was created by Jacques Savoy and is distributed under the BSD license.
+# See http://members.unine.ch/jacques.savoy/clef/index.html.
+# Also see http://www.opensource.org/licenses/bsd-license.html
+# Note: by default this file is used after normalization, so when adding entries
+# to this file, use the arabic 'ي' instead of 'ی'
+انان
+نداشته
+سراسر
+خياه
+ايشان
+وي
+تاكنون
+بيشتري
+دوم
+پس
+ناشي
+وگو
+يا
+داشتند
+سپس
+هنگام
+هرگز
+پنج
+نشان
+امسال
+ديگر
+گروهي
+شدند
+چطور
+ده
+دو
+نخستين
+ولي
+چرا
+چه
+وسط
+كدام
+قابل
+يك
+رفت
+هفت
+همچنين
+در
+هزار
+بله
+بلي
+شايد
+اما
+شناسي
+گرفته
+دهد
+داشته
+دانست
+داشتن
+خواهيم
+ميليارد
+وقتيكه
+امد
+خواهد
+جز
+اورده
+شده
+بلكه
+خدمات
+شدن
+برخي
+نبود
+بسياري
+جلوگيري
+حق
+كردند
+نوعي
+بعري
+نكرده
+نظير
+نبايد
+بوده
+بودن
+داد
+اورد
+هست
+جايي
+شود
+دنبال
+داده
+بايد
+سابق
+هيچ
+همان
+انجا
+كمتر
+كجاست
+گردد
+كسي
+تر
+مردم
+تان
+دادن
+بودند
+سري
+جدا
+ندارند
+مگر
+يكديگر
+دارد
+دهند
+بنابراين
+هنگامي
+سمت
+جا
+انچه
+خود
+دادند
+زياد
+دارند
+اثر
+بدون
+بهترين
+بيشتر
+البته
+به
+براساس
+بيرون
+كرد
+بعضي
+گرفت
+توي
+اي
+ميليون
+او
+جريان
+تول
+بر
+مانند
+برابر
+باشيم
+مدتي
+گويند
+اكنون
+تا
+تنها
+جديد
+چند
+بي
+نشده
+كردن
+كردم
+گويد
+كرده
+كنيم
+نمي
+نزد
+روي
+قصد
+فقط
+بالاي
+ديگران
+اين
+ديروز
+توسط
+سوم
+ايم
+دانند
+سوي
+استفاده
+شما
+كنار
+داريم
+ساخته
+طور
+امده
+رفته
+نخست
+بيست
+نزديك
+طي
+كنيد
+از
+انها
+تمامي
+داشت
+يكي
+طريق
+اش
+چيست
+روب
+نمايد
+گفت
+چندين
+چيزي
+تواند
+ام
+ايا
+با
+ان
+ايد
+ترين
+اينكه
+ديگري
+راه
+هايي
+بروز
+همچنان
+پاعين
+كس
+حدود
+مختلف
+مقابل
+چيز
+گيرد
+ندارد
+ضد
+همچون
+سازي
+شان
+مورد
+باره
+مرسي
+خويش
+برخوردار
+چون
+خارج
+شش
+هنوز
+تحت
+ضمن
+هستيم
+گفته
+فكر
+بسيار
+پيش
+براي
+روزهاي
+انكه
+نخواهد
+بالا
+كل
+وقتي
+كي
+چنين
+كه
+گيري
+نيست
+است
+كجا
+كند
+نيز
+يابد
+بندي
+حتي
+توانند
+عقب
+خواست
+كنند
+بين
+تمام
+همه
+ما
+باشند
+مثل
+شد
+اري
+باشد
+اره
+طبق
+بعد
+اگر
+صورت
+غير
+جاي
+بيش
+ريزي
+اند
+زيرا
+چگونه
+بار
+لطفا
+مي
+درباره
+من
+ديده
+همين
+گذاري
+برداري
+علت
+گذاشته
+هم
+فوق
+نه
+ها
+شوند
+اباد
+همواره
+هر
+اول
+خواهند
+چهار
+نام
+امروز
+مان
+هاي
+قبل
+كنم
+سعي
+تازه
+را
+هستند
+زير
+جلوي
+عنوان
+بود
diff --git a/solr/conf/lang/stopwords_fi.txt b/solr/conf/lang/stopwords_fi.txt
new file mode 100644
index 000000000..4372c9a05
--- /dev/null
+++ b/solr/conf/lang/stopwords_fi.txt
@@ -0,0 +1,97 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/finnish/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+| forms of BE
+
+olla
+olen
+olet
+on
+olemme
+olette
+ovat
+ole | negative form
+
+oli
+olisi
+olisit
+olisin
+olisimme
+olisitte
+olisivat
+olit
+olin
+olimme
+olitte
+olivat
+ollut
+olleet
+
+en | negation
+et
+ei
+emme
+ette
+eivät
+
+|Nom Gen Acc Part Iness Elat Illat Adess Ablat Allat Ess Trans
+minä minun minut minua minussa minusta minuun minulla minulta minulle | I
+sinä sinun sinut sinua sinussa sinusta sinuun sinulla sinulta sinulle | you
+hän hänen hänet häntä hänessä hänestä häneen hänellä häneltä hänelle | he she
+me meidän meidät meitä meissä meistä meihin meillä meiltä meille | we
+te teidän teidät teitä teissä teistä teihin teillä teiltä teille | you
+he heidän heidät heitä heissä heistä heihin heillä heiltä heille | they
+
+tämä tämän tätä tässä tästä tähän tallä tältä tälle tänä täksi | this
+tuo tuon tuotä tuossa tuosta tuohon tuolla tuolta tuolle tuona tuoksi | that
+se sen sitä siinä siitä siihen sillä siltä sille sinä siksi | it
+nämä näiden näitä näissä näistä näihin näillä näiltä näille näinä näiksi | these
+nuo noiden noita noissa noista noihin noilla noilta noille noina noiksi | those
+ne niiden niitä niissä niistä niihin niillä niiltä niille niinä niiksi | they
+
+kuka kenen kenet ketä kenessä kenestä keneen kenellä keneltä kenelle kenenä keneksi| who
+ketkä keiden ketkä keitä keissä keistä keihin keillä keiltä keille keinä keiksi | (pl)
+mikä minkä minkä mitä missä mistä mihin millä miltä mille minä miksi | which what
+mitkä | (pl)
+
+joka jonka jota jossa josta johon jolla jolta jolle jona joksi | who which
+jotka joiden joita joissa joista joihin joilla joilta joille joina joiksi | (pl)
+
+| conjunctions
+
+että | that
+ja | and
+jos | if
+koska | because
+kuin | than
+mutta | but
+niin | so
+sekä | and
+sillä | for
+tai | or
+vaan | but
+vai | or
+vaikka | although
+
+
+| prepositions
+
+kanssa | with
+mukaan | according to
+noin | about
+poikki | across
+yli | over, across
+
+| other
+
+kun | when
+niin | so
+nyt | now
+itse | self
+
diff --git a/solr/conf/lang/stopwords_fr.txt b/solr/conf/lang/stopwords_fr.txt
new file mode 100644
index 000000000..749abae68
--- /dev/null
+++ b/solr/conf/lang/stopwords_fr.txt
@@ -0,0 +1,186 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/french/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | A French stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+au | a + le
+aux | a + les
+avec | with
+ce | this
+ces | these
+dans | with
+de | of
+des | de + les
+du | de + le
+elle | she
+en | `of them' etc
+et | and
+eux | them
+il | he
+je | I
+la | the
+le | the
+leur | their
+lui | him
+ma | my (fem)
+mais | but
+me | me
+même | same; as in moi-même (myself) etc
+mes | me (pl)
+moi | me
+mon | my (masc)
+ne | not
+nos | our (pl)
+notre | our
+nous | we
+on | one
+ou | where
+par | by
+pas | not
+pour | for
+qu | que before vowel
+que | that
+qui | who
+sa | his, her (fem)
+se | oneself
+ses | his (pl)
+son | his, her (masc)
+sur | on
+ta | thy (fem)
+te | thee
+tes | thy (pl)
+toi | thee
+ton | thy (masc)
+tu | thou
+un | a
+une | a
+vos | your (pl)
+votre | your
+vous | you
+
+ | single letter forms
+
+c | c'
+d | d'
+j | j'
+l | l'
+à | to, at
+m | m'
+n | n'
+s | s'
+t | t'
+y | there
+
+ | forms of être (not including the infinitive):
+été
+étée
+étées
+étés
+étant
+suis
+es
+est
+sommes
+êtes
+sont
+serai
+seras
+sera
+serons
+serez
+seront
+serais
+serait
+serions
+seriez
+seraient
+étais
+était
+étions
+étiez
+étaient
+fus
+fut
+fûmes
+fûtes
+furent
+sois
+soit
+soyons
+soyez
+soient
+fusse
+fusses
+fût
+fussions
+fussiez
+fussent
+
+ | forms of avoir (not including the infinitive):
+ayant
+eu
+eue
+eues
+eus
+ai
+as
+avons
+avez
+ont
+aurai
+auras
+aura
+aurons
+aurez
+auront
+aurais
+aurait
+aurions
+auriez
+auraient
+avais
+avait
+avions
+aviez
+avaient
+eut
+eûmes
+eûtes
+eurent
+aie
+aies
+ait
+ayons
+ayez
+aient
+eusse
+eusses
+eût
+eussions
+eussiez
+eussent
+
+ | Later additions (from Jean-Christophe Deschamps)
+ceci | this
+cela | that
+celà | that
+cet | this
+cette | this
+ici | here
+ils | they
+les | the (pl)
+leurs | their (pl)
+quel | which
+quels | which
+quelle | which
+quelles | which
+sans | without
+soi | oneself
+
diff --git a/solr/conf/lang/stopwords_ga.txt b/solr/conf/lang/stopwords_ga.txt
new file mode 100644
index 000000000..9ff88d747
--- /dev/null
+++ b/solr/conf/lang/stopwords_ga.txt
@@ -0,0 +1,110 @@
+
+a
+ach
+ag
+agus
+an
+aon
+ar
+arna
+as
+b'
+ba
+beirt
+bhúr
+caoga
+ceathair
+ceathrar
+chomh
+chtó
+chuig
+chun
+cois
+céad
+cúig
+cúigear
+d'
+daichead
+dar
+de
+deich
+deichniúr
+den
+dhá
+do
+don
+dtí
+dá
+dár
+dó
+faoi
+faoin
+faoina
+faoinár
+fara
+fiche
+gach
+gan
+go
+gur
+haon
+hocht
+i
+iad
+idir
+in
+ina
+ins
+inár
+is
+le
+leis
+lena
+lenár
+m'
+mar
+mo
+mé
+na
+nach
+naoi
+naonúr
+ná
+ní
+níor
+nó
+nócha
+ocht
+ochtar
+os
+roimh
+sa
+seacht
+seachtar
+seachtó
+seasca
+seisear
+siad
+sibh
+sinn
+sna
+sé
+sí
+tar
+thar
+thú
+triúr
+trí
+trína
+trínár
+tríocha
+tú
+um
+ár
+éis
+ón
+óna
+ónár
diff --git a/solr/conf/lang/stopwords_gl.txt b/solr/conf/lang/stopwords_gl.txt
new file mode 100644
index 000000000..d8760b12c
--- /dev/null
+++ b/solr/conf/lang/stopwords_gl.txt
@@ -0,0 +1,161 @@
+# galican stopwords
+a
+aínda
+alí
+aquel
+aquela
+aquelas
+aqueles
+aquilo
+aquí
+ao
+aos
+as
+así
+ben
+cando
+che
+co
+coa
+comigo
+con
+connosco
+contigo
+convosco
+coas
+cos
+cun
+cuns
+cunha
+cunhas
+da
+dalgunha
+dalgunhas
+dalgún
+dalgúns
+das
+de
+del
+dela
+delas
+deles
+desde
+deste
+do
+dos
+dun
+duns
+dunha
+dunhas
+e
+el
+ela
+elas
+eles
+en
+era
+eran
+esa
+esas
+ese
+eses
+esta
+estar
+estaba
+está
+están
+este
+estes
+estiven
+estou
+eu
+facer
+foi
+foron
+fun
+había
+hai
+iso
+isto
+la
+las
+lle
+lles
+lo
+los
+mais
+me
+meu
+meus
+min
+miña
+miñas
+moi
+na
+nas
+neste
+nin
+no
+non
+nos
+nosa
+nosas
+noso
+nosos
+nós
+nun
+nunha
+nuns
+nunhas
+o
+os
+ou
+ós
+para
+pero
+pode
+pois
+pola
+polas
+polo
+polos
+por
+que
+se
+senón
+ser
+seu
+seus
+sexa
+sido
+sobre
+súa
+súas
+tamén
+tan
+te
+ten
+teñen
+teño
+ter
+teu
+teus
+ti
+tido
+tiña
+tiven
+túa
+túas
+un
+unha
+unhas
+uns
+vos
+vosa
+vosas
+voso
+vosos
+vós
diff --git a/solr/conf/lang/stopwords_hi.txt b/solr/conf/lang/stopwords_hi.txt
new file mode 100644
index 000000000..86286bb08
--- /dev/null
+++ b/solr/conf/lang/stopwords_hi.txt
@@ -0,0 +1,235 @@
+# Also see http://www.opensource.org/licenses/bsd-license.html
+# See http://members.unine.ch/jacques.savoy/clef/index.html.
+# This file was created by Jacques Savoy and is distributed under the BSD license.
+# Note: by default this file also contains forms normalized by HindiNormalizer
+# for spelling variation (see section below), such that it can be used whether or
+# not you enable that feature. When adding additional entries to this list,
+# please add the normalized form as well.
+अंदर
+अत
+अपना
+अपनी
+अपने
+अभी
+आदि
+आप
+इत्यादि
+इन
+इनका
+इन्हीं
+इन्हें
+इन्हों
+इस
+इसका
+इसकी
+इसके
+इसमें
+इसी
+इसे
+उन
+उनका
+उनकी
+उनके
+उनको
+उन्हीं
+उन्हें
+उन्हों
+उस
+उसके
+उसी
+उसे
+एक
+एवं
+एस
+ऐसे
+और
+कई
+कर
+करता
+करते
+करना
+करने
+करें
+कहते
+कहा
+का
+काफ़ी
+कि
+कितना
+किन्हें
+किन्हों
+किया
+किर
+किस
+किसी
+किसे
+की
+कुछ
+कुल
+के
+को
+कोई
+कौन
+कौनसा
+गया
+घर
+जब
+जहाँ
+जा
+जितना
+जिन
+जिन्हें
+जिन्हों
+जिस
+जिसे
+जीधर
+जैसा
+जैसे
+जो
+तक
+तब
+तरह
+तिन
+तिन्हें
+तिन्हों
+तिस
+तिसे
+तो
+था
+थी
+थे
+दबारा
+दिया
+दुसरा
+दूसरे
+दो
+द्वारा
+न
+नहीं
+ना
+निहायत
+नीचे
+ने
+पर
+पर
+पहले
+पूरा
+पे
+फिर
+बनी
+बही
+बहुत
+बाद
+बाला
+बिलकुल
+भी
+भीतर
+मगर
+मानो
+मे
+में
+यदि
+यह
+यहाँ
+यही
+या
+यिह
+ये
+रखें
+रहा
+रहे
+ऱ्वासा
+लिए
+लिये
+लेकिन
+व
+वर्ग
+वह
+वह
+वहाँ
+वहीं
+वाले
+वुह
+वे
+वग़ैरह
+संग
+सकता
+सकते
+सबसे
+सभी
+साथ
+साबुत
+साभ
+सारा
+से
+सो
+ही
+हुआ
+हुई
+हुए
+है
+हैं
+हो
+होता
+होती
+होते
+होना
+होने
+# additional normalized forms of the above
+अपनि
+जेसे
+होति
+सभि
+तिंहों
+इंहों
+दवारा
+इसि
+किंहें
+थि
+उंहों
+ओर
+जिंहें
+वहिं
+अभि
+बनि
+हि
+उंहिं
+उंहें
+हें
+वगेरह
+एसे
+रवासा
+कोन
+निचे
+काफि
+उसि
+पुरा
+भितर
+हे
+बहि
+वहां
+कोइ
+यहां
+जिंहों
+तिंहें
+किसि
+कइ
+यहि
+इंहिं
+जिधर
+इंहें
+अदि
+इतयादि
+हुइ
+कोनसा
+इसकि
+दुसरे
+जहां
+अप
+किंहों
+उनकि
+भि
+वरग
+हुअ
+जेसा
+नहिं
diff --git a/solr/conf/lang/stopwords_hu.txt b/solr/conf/lang/stopwords_hu.txt
new file mode 100644
index 000000000..37526da8a
--- /dev/null
+++ b/solr/conf/lang/stopwords_hu.txt
@@ -0,0 +1,211 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/hungarian/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+| Hungarian stop word list
+| prepared by Anna Tordai
+
+a
+ahogy
+ahol
+aki
+akik
+akkor
+alatt
+által
+általában
+amely
+amelyek
+amelyekben
+amelyeket
+amelyet
+amelynek
+ami
+amit
+amolyan
+amíg
+amikor
+át
+abban
+ahhoz
+annak
+arra
+arról
+az
+azok
+azon
+azt
+azzal
+azért
+aztán
+azután
+azonban
+bár
+be
+belül
+benne
+cikk
+cikkek
+cikkeket
+csak
+de
+e
+eddig
+egész
+egy
+egyes
+egyetlen
+egyéb
+egyik
+egyre
+ekkor
+el
+elég
+ellen
+elő
+először
+előtt
+első
+én
+éppen
+ebben
+ehhez
+emilyen
+ennek
+erre
+ez
+ezt
+ezek
+ezen
+ezzel
+ezért
+és
+fel
+felé
+hanem
+hiszen
+hogy
+hogyan
+igen
+így
+illetve
+ill.
+ill
+ilyen
+ilyenkor
+ison
+ismét
+itt
+jó
+jól
+jobban
+kell
+kellett
+keresztül
+keressünk
+ki
+kívül
+között
+közül
+legalább
+lehet
+lehetett
+legyen
+lenne
+lenni
+lesz
+lett
+maga
+magát
+majd
+majd
+már
+más
+másik
+meg
+még
+mellett
+mert
+mely
+melyek
+mi
+mit
+míg
+miért
+milyen
+mikor
+minden
+mindent
+mindenki
+mindig
+mint
+mintha
+mivel
+most
+nagy
+nagyobb
+nagyon
+ne
+néha
+nekem
+neki
+nem
+néhány
+nélkül
+nincs
+olyan
+ott
+össze
+ők
+őket
+pedig
+persze
+rá
+s
+saját
+sem
+semmi
+sok
+sokat
+sokkal
+számára
+szemben
+szerint
+szinte
+talán
+tehát
+teljes
+tovább
+továbbá
+több
+úgy
+ugyanis
+új
+újabb
+újra
+után
+utána
+utolsó
+vagy
+vagyis
+valaki
+valami
+valamint
+való
+vagyok
+van
+vannak
+volt
+voltam
+voltak
+voltunk
+vissza
+vele
+viszont
+volna
diff --git a/solr/conf/lang/stopwords_hy.txt b/solr/conf/lang/stopwords_hy.txt
new file mode 100644
index 000000000..60c1c50fb
--- /dev/null
+++ b/solr/conf/lang/stopwords_hy.txt
@@ -0,0 +1,46 @@
+# example set of Armenian stopwords.
+այդ
+այլ
+այն
+այս
+դու
+դուք
+եմ
+են
+ենք
+ես
+եք
+էի
+էին
+էինք
+էիր
+էիք
+էր
+ըստ
+ին
+իսկ
+իր
+կամ
+համար
+հետ
+հետո
+մենք
+մեջ
+մի
+նա
+նաև
+նրա
+նրանք
+որ
+որը
+որոնք
+որպես
+ու
+ում
+պիտի
+վրա
diff --git a/solr/conf/lang/stopwords_id.txt b/solr/conf/lang/stopwords_id.txt
new file mode 100644
index 000000000..4617f83a5
--- /dev/null
+++ b/solr/conf/lang/stopwords_id.txt
@@ -0,0 +1,359 @@
+# from appendix D of: A Study of Stemming Effects on Information
+# Retrieval in Bahasa Indonesia
+ada
+adanya
+adalah
+adapun
+agak
+agaknya
+agar
+akan
+akankah
+akhirnya
+aku
+akulah
+amat
+amatlah
+anda
+andalah
+antar
+diantaranya
+antara
+antaranya
+diantara
+apa
+apaan
+mengapa
+apabila
+apakah
+apalagi
+apatah
+atau
+ataukah
+ataupun
+bagai
+bagaikan
+sebagai
+sebagainya
+bagaimana
+bagaimanapun
+sebagaimana
+bagaimanakah
+bagi
+bahkan
+bahwa
+bahwasanya
+sebaliknya
+banyak
+sebanyak
+beberapa
+seberapa
+begini
+beginian
+beginikah
+beginilah
+sebegini
+begitu
+begitukah
+begitulah
+begitupun
+sebegitu
+belum
+belumlah
+sebelum
+sebelumnya
+sebenarnya
+berapa
+berapakah
+berapalah
+berapapun
+betulkah
+sebetulnya
+biasa
+biasanya
+bila
+bilakah
+bisa
+bisakah
+sebisanya
+boleh
+bolehkah
+bolehlah
+buat
+bukan
+bukankah
+bukanlah
+bukannya
+cuma
+percuma
+dahulu
+dalam
+dan
+dapat
+dari
+daripada
+dekat
+demi
+demikian
+demikianlah
+sedemikian
+dengan
+depan
+di
+dia
+dialah
+dini
+diri
+dirinya
+terdiri
+dong
+dulu
+enggak
+enggaknya
+entah
+entahlah
+terhadap
+terhadapnya
+hal
+hampir
+hanya
+hanyalah
+harus
+haruslah
+harusnya
+seharusnya
+hendak
+hendaklah
+hendaknya
+hingga
+sehingga
+ia
+ialah
+ibarat
+ingin
+inginkah
+inginkan
+ini
+inikah
+inilah
+itu
+itukah
+itulah
+jangan
+jangankan
+janganlah
+jika
+jikalau
+juga
+justru
+kala
+kalau
+kalaulah
+kalaupun
+kalian
+kami
+kamilah
+kamu
+kamulah
+kan
+kapan
+kapankah
+kapanpun
+dikarenakan
+karena
+karenanya
+ke
+kecil
+kemudian
+kenapa
+kepada
+kepadanya
+ketika
+seketika
+khususnya
+kini
+kinilah
+kiranya
+sekiranya
+kita
+kitalah
+kok
+lagi
+lagian
+selagi
+lah
+lain
+lainnya
+melainkan
+selaku
+lalu
+melalui
+terlalu
+lama
+lamanya
+selama
+selama
+selamanya
+lebih
+terlebih
+bermacam
+macam
+semacam
+maka
+makanya
+makin
+malah
+malahan
+mampu
+mampukah
+mana
+manakala
+manalagi
+masih
+masihkah
+semasih
+masing
+mau
+maupun
+semaunya
+memang
+mereka
+merekalah
+meski
+meskipun
+semula
+mungkin
+mungkinkah
+nah
+namun
+nanti
+nantinya
+nyaris
+oleh
+olehnya
+seorang
+seseorang
+pada
+padanya
+padahal
+paling
+sepanjang
+pantas
+sepantasnya
+sepantasnyalah
+para
+pasti
+pastilah
+per
+pernah
+pula
+pun
+merupakan
+rupanya
+serupa
+saat
+saatnya
+sesaat
+saja
+sajalah
+saling
+bersama
+sama
+sesama
+sambil
+sampai
+sana
+sangat
+sangatlah
+saya
+sayalah
+se
+sebab
+sebabnya
+sebuah
+tersebut
+tersebutlah
+sedang
+sedangkan
+sedikit
+sedikitnya
+segala
+segalanya
+segera
+sesegera
+sejak
+sejenak
+sekali
+sekalian
+sekalipun
+sesekali
+sekaligus
+sekarang
+sekarang
+sekitar
+sekitarnya
+sela
+selain
+selalu
+seluruh
+seluruhnya
+semakin
+sementara
+sempat
+semua
+semuanya
+sendiri
+sendirinya
+seolah
+seperti
+sepertinya
+sering
+seringnya
+serta
+siapa
+siapakah
+siapapun
+disini
+disinilah
+sini
+sinilah
+sesuatu
+sesuatunya
+suatu
+sesudah
+sesudahnya
+sudah
+sudahkah
+sudahlah
+supaya
+tadi
+tadinya
+tak
+tanpa
+setelah
+telah
+tentang
+tentu
+tentulah
+tentunya
+tertentu
+seterusnya
+tapi
+tetapi
+setiap
+tiap
+setidaknya
+tidak
+tidakkah
+tidaklah
+toh
+waduh
+wah
+wahai
+sewaktu
+walau
+walaupun
+wong
+yaitu
+yakni
+yang
diff --git a/solr/conf/lang/stopwords_it.txt b/solr/conf/lang/stopwords_it.txt
new file mode 100644
index 000000000..1219cc773
--- /dev/null
+++ b/solr/conf/lang/stopwords_it.txt
@@ -0,0 +1,303 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/italian/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | An Italian stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+ad | a (to) before vowel
+al | a + il
+allo | a + lo
+ai | a + i
+agli | a + gli
+all | a + l'
+agl | a + gl'
+alla | a + la
+alle | a + le
+con | with
+col | con + il
+coi | con + i (forms collo, cogli etc are now very rare)
+da | from
+dal | da + il
+dallo | da + lo
+dai | da + i
+dagli | da + gli
+dall | da + l'
+dagl | da + gll'
+dalla | da + la
+dalle | da + le
+di | of
+del | di + il
+dello | di + lo
+dei | di + i
+degli | di + gli
+dell | di + l'
+degl | di + gl'
+della | di + la
+delle | di + le
+in | in
+nel | in + el
+nello | in + lo
+nei | in + i
+negli | in + gli
+nell | in + l'
+negl | in + gl'
+nella | in + la
+nelle | in + le
+su | on
+sul | su + il
+sullo | su + lo
+sui | su + i
+sugli | su + gli
+sull | su + l'
+sugl | su + gl'
+sulla | su + la
+sulle | su + le
+per | through, by
+tra | among
+contro | against
+io | I
+tu | thou
+lui | he
+lei | she
+noi | we
+voi | you
+loro | they
+mio | my
+mia |
+miei |
+mie |
+tuo |
+tua |
+tuoi | thy
+tue |
+suo |
+sua |
+suoi | his, her
+sue |
+nostro | our
+nostra |
+nostri |
+nostre |
+vostro | your
+vostra |
+vostri |
+vostre |
+mi | me
+ti | thee
+ci | us, there
+vi | you, there
+lo | him, the
+la | her, the
+li | them
+le | them, the
+gli | to him, the
+ne | from there etc
+il | the
+un | a
+uno | a
+una | a
+ma | but
+ed | and
+se | if
+perché | why, because
+anche | also
+come | how
+dov | where (as dov')
+dove | where
+che | who, that
+chi | who
+cui | whom
+non | not
+più | more
+quale | who, that
+quanto | how much
+quanti |
+quanta |
+quante |
+quello | that
+quelli |
+quella |
+quelle |
+questo | this
+questi |
+questa |
+queste |
+si | yes
+tutto | all
+tutti | all
+
+ | single letter forms:
+
+a | at
+c | as c' for ce or ci
+e | and
+i | the
+l | as l'
+o | or
+
+ | forms of avere, to have (not including the infinitive):
+
+ho
+hai
+ha
+abbiamo
+avete
+hanno
+abbia
+abbiate
+abbiano
+avrò
+avrai
+avrà
+avremo
+avrete
+avranno
+avrei
+avresti
+avrebbe
+avremmo
+avreste
+avrebbero
+avevo
+avevi
+aveva
+avevamo
+avevate
+avevano
+ebbi
+avesti
+ebbe
+avemmo
+aveste
+ebbero
+avessi
+avesse
+avessimo
+avessero
+avendo
+avuto
+avuta
+avuti
+avute
+
+ | forms of essere, to be (not including the infinitive):
+sono
+sei
+siamo
+siete
+sia
+siate
+siano
+sarò
+sarai
+sarà
+saremo
+sarete
+saranno
+sarei
+saresti
+sarebbe
+saremmo
+sareste
+sarebbero
+ero
+eri
+era
+eravamo
+eravate
+erano
+fui
+fosti
+fu
+fummo
+foste
+furono
+fossi
+fosse
+fossimo
+fossero
+essendo
+
+ | forms of fare, to do (not including the infinitive, fa, fat-):
+faccio
+fai
+facciamo
+fanno
+faccia
+facciate
+facciano
+farò
+farai
+farà
+faremo
+farete
+faranno
+farei
+faresti
+farebbe
+faremmo
+fareste
+farebbero
+facevo
+facevi
+faceva
+facevamo
+facevate
+facevano
+feci
+facesti
+fece
+facemmo
+faceste
+fecero
+facessi
+facesse
+facessimo
+facessero
+facendo
+
+ | forms of stare, to be (not including the infinitive):
+sto
+stai
+sta
+stiamo
+stanno
+stia
+stiate
+stiano
+starò
+starai
+starà
+staremo
+starete
+staranno
+starei
+staresti
+starebbe
+staremmo
+stareste
+starebbero
+stavo
+stavi
+stava
+stavamo
+stavate
+stavano
+stetti
+stesti
+stette
+stemmo
+steste
+stettero
+stessi
+stesse
+stessimo
+stessero
+stando
diff --git a/solr/conf/lang/stopwords_ja.txt b/solr/conf/lang/stopwords_ja.txt
new file mode 100644
index 000000000..d4321be6b
--- /dev/null
+++ b/solr/conf/lang/stopwords_ja.txt
@@ -0,0 +1,127 @@
+#
+# This file defines a stopword set for Japanese.
+#
+# This set is made up of hand-picked frequent terms from segmented Japanese Wikipedia.
+# Punctuation characters and frequent kanji have mostly been left out. See LUCENE-3745
+# for frequency lists, etc. that can be useful for making your own set (if desired)
+#
+# Note that there is an overlap between these stopwords and the terms stopped when used
+# in combination with the JapanesePartOfSpeechStopFilter. When editing this file, note
+# that comments are not allowed on the same line as stopwords.
+#
+# Also note that stopping is done in a case-insensitive manner. Change your StopFilter
+# configuration if you need case-sensitive stopping. Lastly, note that stopping is done
+# using the same character width as the entries in this file. Since this StopFilter is
+# normally done after a CJKWidthFilter in your chain, you would usually want your romaji
+# entries to be in half-width and your kana entries to be in full-width.
+#
+の
+に
+は
+を
+た
+が
+で
+て
+と
+し
+れ
+さ
+ある
+いる
+も
+する
+から
+な
+こと
+として
+い
+や
+れる
+など
+なっ
+ない
+この
+ため
+その
+あっ
+よう
+また
+もの
+という
+あり
+まで
+られ
+なる
+へ
+か
+だ
+これ
+によって
+により
+おり
+より
+による
+ず
+なり
+られる
+において
+ば
+なかっ
+なく
+しかし
+について
+せ
+だっ
+その後
+できる
+それ
+う
+ので
+なお
+のみ
+でき
+き
+つ
+における
+および
+いう
+さらに
+でも
+ら
+たり
+その他
+に関する
+たち
+ます
+ん
+なら
+に対して
+特に
+せる
+及び
+これら
+とき
+では
+にて
+ほか
+ながら
+うち
+そして
+とともに
+ただし
+かつて
+それぞれ
+または
+お
+ほど
+ものの
+に対する
+ほとんど
+と共に
+といった
+です
+とも
+ところ
+ここ
+##### End of file
diff --git a/solr/conf/lang/stopwords_lv.txt b/solr/conf/lang/stopwords_lv.txt
new file mode 100644
index 000000000..e21a23c06
--- /dev/null
+++ b/solr/conf/lang/stopwords_lv.txt
@@ -0,0 +1,172 @@
+# Set of Latvian stopwords from A Stemming Algorithm for Latvian, Karlis Kreslins
+# the original list of over 800 forms was refined:
+# pronouns, adverbs, interjections were removed
+#
+# prepositions
+aiz
+ap
+ar
+apakš
+ārpus
+augšpus
+bez
+caur
+dēļ
+gar
+iekš
+iz
+kopš
+labad
+lejpus
+līdz
+no
+otrpus
+pa
+par
+pār
+pēc
+pie
+pirms
+pret
+priekš
+starp
+šaipus
+uz
+viņpus
+virs
+virspus
+zem
+apakšpus
+# Conjunctions
+un
+bet
+jo
+ja
+ka
+lai
+tomēr
+tikko
+turpretī
+arī
+kaut
+gan
+tādēļ
+tā
+ne
+tikvien
+vien
+kā
+ir
+te
+vai
+kamēr
+# Particles
+ar
+diezin
+droši
+diemžēl
+nebūt
+ik
+it
+taču
+nu
+pat
+tiklab
+iekšpus
+nedz
+tik
+nevis
+turpretim
+jeb
+iekam
+iekām
+iekāms
+kolīdz
+līdzko
+tiklīdz
+jebšu
+tālab
+tāpēc
+nekā
+itin
+jā
+jau
+jel
+nē
+nezin
+tad
+tikai
+vis
+tak
+iekams
+vien
+# modal verbs
+būt
+biju
+biji
+bija
+bijām
+bijāt
+esmu
+esi
+esam
+esat
+būšu
+būsi
+būs
+būsim
+būsiet
+tikt
+tiku
+tiki
+tika
+tikām
+tikāt
+tieku
+tiec
+tiek
+tiekam
+tiekat
+tikšu
+tiks
+tiksim
+tiksiet
+tapt
+tapi
+tapāt
+topat
+tapšu
+tapsi
+taps
+tapsim
+tapsiet
+kļūt
+kļuvu
+kļuvi
+kļuva
+kļuvām
+kļuvāt
+kļūstu
+kļūsti
+kļūst
+kļūstam
+kļūstat
+kļūšu
+kļūsi
+kļūs
+kļūsim
+kļūsiet
+# verbs
+varēt
+varēju
+varējām
+varēšu
+varēsim
+var
+varēji
+varējāt
+varēsi
+varēsiet
+varat
+varēja
+varēs
diff --git a/solr/conf/lang/stopwords_nl.txt b/solr/conf/lang/stopwords_nl.txt
new file mode 100644
index 000000000..47a2aeacf
--- /dev/null
+++ b/solr/conf/lang/stopwords_nl.txt
@@ -0,0 +1,119 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/dutch/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | A Dutch stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+ | This is a ranked list (commonest to rarest) of stopwords derived from
+ | a large sample of Dutch text.
+
+ | Dutch stop words frequently exhibit homonym clashes. These are indicated
+ | clearly below.
+
+de | the
+en | and
+van | of, from
+ik | I, the ego
+te | (1) chez, at etc, (2) to, (3) too
+dat | that, which
+die | that, those, who, which
+in | in, inside
+een | a, an, one
+hij | he
+het | the, it
+niet | not, nothing, naught
+zijn | (1) to be, being, (2) his, one's, its
+is | is
+was | (1) was, past tense of all persons sing. of 'zijn' (to be) (2) wax, (3) the washing, (4) rise of river
+op | on, upon, at, in, up, used up
+aan | on, upon, to (as dative)
+met | with, by
+als | like, such as, when
+voor | (1) before, in front of, (2) furrow
+had | had, past tense all persons sing. of 'hebben' (have)
+er | there
+maar | but, only
+om | round, about, for etc
+hem | him
+dan | then
+zou | should/would, past tense all persons sing. of 'zullen'
+of | or, whether, if
+wat | what, something, anything
+mijn | possessive and noun 'mine'
+men | people, 'one'
+dit | this
+zo | so, thus, in this way
+door | through by
+over | over, across
+ze | she, her, they, them
+zich | oneself
+bij | (1) a bee, (2) by, near, at
+ook | also, too
+tot | till, until
+je | you
+mij | me
+uit | out of, from
+der | Old Dutch form of 'van der' still found in surnames
+daar | (1) there, (2) because
+haar | (1) her, their, them, (2) hair
+naar | (1) unpleasant, unwell etc, (2) towards, (3) as
+heb | present first person sing. of 'to have'
+hoe | how, why
+heeft | present third person sing. of 'to have'
+hebben | 'to have' and various parts thereof
+deze | this
+u | you
+want | (1) for, (2) mitten, (3) rigging
+nog | yet, still
+zal | 'shall', first and third person sing. of verb 'zullen' (will)
+me | me
+zij | she, they
+nu | now
+ge | 'thou', still used in Belgium and south Netherlands
+geen | none
+omdat | because
+iets | something, somewhat
+worden | to become, grow, get
+toch | yet, still
+al | all, every, each
+waren | (1) 'were' (2) to wander, (3) wares, (3)
+veel | much, many
+meer | (1) more, (2) lake
+doen | to do, to make
+toen | then, when
+moet | noun 'spot/mote' and present form of 'to must'
+ben | (1) am, (2) 'are' in interrogative second person singular of 'to be'
+zonder | without
+kan | noun 'can' and present form of 'to be able'
+hun | their, them
+dus | so, consequently
+alles | all, everything, anything
+onder | under, beneath
+ja | yes, of course
+eens | once, one day
+hier | here
+wie | who
+werd | imperfect third person sing. of 'become'
+altijd | always
+doch | yet, but etc
+wordt | present third person sing. of 'become'
+wezen | (1) to be, (2) 'been' as in 'been fishing', (3) orphans
+kunnen | to be able
+ons | us/our
+zelf | self
+tegen | against, towards, at
+na | after, near
+reeds | already
+wil | (1) present tense of 'want', (2) 'will', noun, (3) fender
+kon | could; past tense of 'to be able'
+niets | nothing
+uw | your
+iemand | somebody
+geweest | been; past participle of 'be'
+andere | other
diff --git a/solr/conf/lang/stopwords_no.txt b/solr/conf/lang/stopwords_no.txt
new file mode 100644
index 000000000..a7a2c28ba
--- /dev/null
+++ b/solr/conf/lang/stopwords_no.txt
@@ -0,0 +1,194 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/norwegian/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | A Norwegian stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+ | This stop word list is for the dominant bokmål dialect. Words unique
+ | to nynorsk are marked *.
+
+ | Revised by Jan Bruusgaard <Jan.Bruusgaard@ssb.no>, Jan 2005
+
+og | and
+i | in
+jeg | I
+det | it/this/that
+at | to (w. inf.)
+en | a/an
+et | a/an
+den | it/this/that
+til | to
+er | is/am/are
+som | who/that
+på | on
+de | they / you(formal)
+med | with
+han | he
+av | of
+ikke | not
+ikkje | not *
+der | there
+så | so
+var | was/were
+meg | me
+seg | you
+men | but
+ett | one
+har | have
+om | about
+vi | we
+min | my
+mitt | my
+ha | have
+hadde | had
+hun | she
+nå | now
+over | over
+da | when/as
+ved | by/know
+fra | from
+du | you
+ut | out
+sin | your
+dem | them
+oss | us
+opp | up
+man | you/one
+kan | can
+hans | his
+hvor | where
+eller | or
+hva | what
+skal | shall/must
+selv | self (reflective)
+sjøl | self (reflective)
+her | here
+alle | all
+vil | will
+bli | become
+ble | became
+blei | became *
+blitt | have become
+kunne | could
+inn | in
+når | when
+være | be
+kom | come
+noen | some
+noe | some
+ville | would
+dere | you
+som | who/which/that
+deres | their/theirs
+kun | only/just
+ja | yes
+etter | after
+ned | down
+skulle | should
+denne | this
+for | for/because
+deg | you
+si | hers/his
+sine | hers/his
+sitt | hers/his
+mot | against
+å | to
+meget | much
+hvorfor | why
+dette | this
+disse | these/those
+uten | without
+hvordan | how
+ingen | none
+din | your
+ditt | your
+blir | become
+samme | same
+hvilken | which
+hvilke | which (plural)
+sånn | such a
+inni | inside/within
+mellom | between
+vår | our
+hver | each
+hvem | who
+vors | us/ours
+hvis | whose
+både | both
+bare | only/just
+enn | than
+fordi | as/because
+før | before
+mange | many
+også | also
+slik | just
+vært | been
+være | to be
+båe | both *
+begge | both
+siden | since
+dykk | your *
+dykkar | yours *
+dei | they *
+deira | them *
+deires | theirs *
+deim | them *
+di | your (fem.) *
+då | as/when *
+eg | I *
+ein | a/an *
+eit | a/an *
+eitt | a/an *
+elles | or *
+honom | he *
+hjå | at *
+ho | she *
+hoe | she *
+henne | her
+hennar | her/hers
+hennes | hers
+hoss | how *
+hossen | how *
+ikkje | not *
+ingi | noone *
+inkje | noone *
+korleis | how *
+korso | how *
+kva | what/which *
+kvar | where *
+kvarhelst | where *
+kven | who/whom *
+kvi | why *
+kvifor | why *
+me | we *
+medan | while *
+mi | my *
+mine | my *
+mykje | much *
+no | now *
+nokon | some (masc./neut.) *
+noka | some (fem.) *
+nokor | some *
+noko | some *
+nokre | some *
+si | his/hers *
+sia | since *
+sidan | since *
+so | so *
+somt | some *
+somme | some *
+um | about*
+upp | up *
+vere | be *
+vore | was *
+verte | become *
+vort | become *
+varte | became *
+vart | became *
+
diff --git a/solr/conf/lang/stopwords_pt.txt b/solr/conf/lang/stopwords_pt.txt
new file mode 100644
index 000000000..acfeb01af
--- /dev/null
+++ b/solr/conf/lang/stopwords_pt.txt
@@ -0,0 +1,253 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/portuguese/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | A Portuguese stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+
+ | The following is a ranked list (commonest to rarest) of stopwords
+ | deriving from a large sample of text.
+
+ | Extra words have been added at the end.
+
+de | of, from
+a | the; to, at; her
+o | the; him
+que | who, that
+e | and
+do | de + o
+da | de + a
+em | in
+um | a
+para | for
+ | é from SER
+com | with
+não | not, no
+uma | a
+os | the; them
+no | em + o
+se | himself etc
+na | em + a
+por | for
+mais | more
+as | the; them
+dos | de + os
+como | as, like
+mas | but
+ | foi from SER
+ao | a + o
+ele | he
+das | de + as
+ | tem from TER
+à | a + a
+seu | his
+sua | her
+ou | or
+ | ser from SER
+quando | when
+muito | much
+ | há from HAV
+nos | em + os; us
+já | already, now
+ | está from EST
+eu | I
+também | also
+só | only, just
+pelo | per + o
+pela | per + a
+até | up to
+isso | that
+ela | he
+entre | between
+ | era from SER
+depois | after
+sem | without
+mesmo | same
+aos | a + os
+ | ter from TER
+seus | his
+quem | whom
+nas | em + as
+me | me
+esse | that
+eles | they
+ | estão from EST
+você | you
+ | tinha from TER
+ | foram from SER
+essa | that
+num | em + um
+nem | nor
+suas | her
+meu | my
+às | a + as
+minha | my
+ | têm from TER
+numa | em + uma
+pelos | per + os
+elas | they
+ | havia from HAV
+ | seja from SER
+qual | which
+ | será from SER
+nós | we
+ | tenho from TER
+lhe | to him, her
+deles | of them
+essas | those
+esses | those
+pelas | per + as
+este | this
+ | fosse from SER
+dele | of him
+
+ | other words. There are many contractions such as naquele = em+aquele,
+ | mo = me+o, but they are rare.
+ | Indefinite article plural forms are also rare.
+
+tu | thou
+te | thee
+vocês | you (plural)
+vos | you
+lhes | to them
+meus | my
+minhas
+teu | thy
+tua
+teus
+tuas
+nosso | our
+nossa
+nossos
+nossas
+
+dela | of her
+delas | of them
+
+esta | this
+estes | these
+estas | these
+aquele | that
+aquela | that
+aqueles | those
+aquelas | those
+isto | this
+aquilo | that
+
+ | forms of estar, to be (not including the infinitive):
+estou
+está
+estamos
+estão
+estive
+esteve
+estivemos
+estiveram
+estava
+estávamos
+estavam
+estivera
+estivéramos
+esteja
+estejamos
+estejam
+estivesse
+estivéssemos
+estivessem
+estiver
+estivermos
+estiverem
+
+ | forms of haver, to have (not including the infinitive):
+hei
+há
+havemos
+hão
+houve
+houvemos
+houveram
+houvera
+houvéramos
+haja
+hajamos
+hajam
+houvesse
+houvéssemos
+houvessem
+houver
+houvermos
+houverem
+houverei
+houverá
+houveremos
+houverão
+houveria
+houveríamos
+houveriam
+
+ | forms of ser, to be (not including the infinitive):
+sou
+somos
+são
+era
+éramos
+eram
+fui
+foi
+fomos
+foram
+fora
+fôramos
+seja
+sejamos
+sejam
+fosse
+fôssemos
+fossem
+for
+formos
+forem
+serei
+será
+seremos
+serão
+seria
+seríamos
+seriam
+
+ | forms of ter, to have (not including the infinitive):
+tenho
+tem
+temos
+tém
+tinha
+tínhamos
+tinham
+tive
+teve
+tivemos
+tiveram
+tivera
+tivéramos
+tenha
+tenhamos
+tenham
+tivesse
+tivéssemos
+tivessem
+tiver
+tivermos
+tiverem
+terei
+terá
+teremos
+terão
+teria
+teríamos
+teriam
diff --git a/solr/conf/lang/stopwords_ro.txt b/solr/conf/lang/stopwords_ro.txt
new file mode 100644
index 000000000..4fdee90a5
--- /dev/null
+++ b/solr/conf/lang/stopwords_ro.txt
@@ -0,0 +1,233 @@
+# This file was created by Jacques Savoy and is distributed under the BSD license.
+# See http://members.unine.ch/jacques.savoy/clef/index.html.
+# Also see http://www.opensource.org/licenses/bsd-license.html
+acea
+aceasta
+această
+aceea
+acei
+aceia
+acel
+acela
+acele
+acelea
+acest
+acesta
+aceste
+acestea
+aceşti
+aceştia
+acolo
+acum
+ai
+aia
+aibă
+aici
+al
+ăla
+ale
+alea
+ălea
+altceva
+altcineva
+am
+ar
+are
+aş
+aşadar
+asemenea
+asta
+ăsta
+astăzi
+astea
+ăstea
+ăştia
+asupra
+aţi
+au
+avea
+avem
+aveţi
+azi
+bine
+bucur
+bună
+ca
+că
+căci
+când
+care
+cărei
+căror
+cărui
+cât
+câte
+câţi
+către
+câtva
+ce
+cel
+ceva
+chiar
+cînd
+cine
+cineva
+cît
+cîte
+cîţi
+cîtva
+contra
+cu
+cum
+cumva
+curând
+curînd
+da
+dă
+dacă
+dar
+datorită
+de
+deci
+deja
+deoarece
+departe
+deşi
+din
+dinaintea
+dintr
+dintre
+drept
+după
+ea
+ei
+el
+ele
+eram
+este
+eşti
+eu
+face
+fără
+fi
+fie
+fiecare
+fii
+fim
+fiţi
+iar
+ieri
+îi
+îl
+îmi
+împotriva
+în
+înainte
+înaintea
+încât
+încît
+încotro
+între
+întrucât
+întrucît
+îţi
+la
+lângă
+le
+li
+lîngă
+lor
+lui
+mă
+mâine
+mea
+mei
+mele
+mereu
+meu
+mi
+mine
+mult
+multă
+mulţi
+ne
+nicăieri
+nici
+nimeni
+nişte
+noastră
+noastre
+noi
+noştri
+nostru
+nu
+ori
+oricând
+oricare
+oricât
+orice
+oricînd
+oricine
+oricît
+oricum
+oriunde
+până
+pe
+pentru
+peste
+pînă
+poate
+pot
+prea
+prima
+primul
+prin
+printr
+sa
+să
+săi
+sale
+sau
+său
+se
+şi
+sînt
+sîntem
+sînteţi
+spre
+sub
+sunt
+suntem
+sunteţi
+ta
+tăi
+tale
+tău
+te
+ţi
+ţie
+tine
+toată
+toate
+tot
+toţi
+totuşi
+tu
+un
+una
+unde
+undeva
+unei
+unele
+uneori
+unor
+vă
+vi
+voastră
+voastre
+voi
+voştri
+vostru
+vouă
+vreo
+vreun
diff --git a/solr/conf/lang/stopwords_ru.txt b/solr/conf/lang/stopwords_ru.txt
new file mode 100644
index 000000000..55271400c
--- /dev/null
+++ b/solr/conf/lang/stopwords_ru.txt
@@ -0,0 +1,243 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/russian/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | a russian stop word list. comments begin with vertical bar. each stop
+ | word is at the start of a line.
+
+ | this is a ranked list (commonest to rarest) of stopwords derived from
+ | a large text sample.
+
+ | letter `ё' is translated to `е'.
+
+и | and
+в | in/into
+во | alternative form
+не | not
+что | what/that
+он | he
+на | on/onto
+я | i
+с | from
+со | alternative form
+как | how
+а | milder form of `no' (but)
+то | conjunction and form of `that'
+все | all
+она | she
+так | so, thus
+его | him
+но | but
+да | yes/and
+ты | thou
+к | towards, by
+у | around, chez
+же | intensifier particle
+вы | you
+за | beyond, behind
+бы | conditional/subj. particle
+по | up to, along
+только | only
+ее | her
+мне | to me
+было | it was
+вот | here is/are, particle
+от | away from
+меня | me
+еще | still, yet, more
+нет | no, there isnt/arent
+о | about
+из | out of
+ему | to him
+теперь | now
+когда | when
+даже | even
+ну | so, well
+вдруг | suddenly
+ли | interrogative particle
+если | if
+уже | already, but homonym of `narrower'
+или | or
+ни | neither
+быть | to be
+был | he was
+него | prepositional form of его
+до | up to
+вас | you accusative
+нибудь | indef. suffix preceded by hyphen
+опять | again
+уж | already, but homonym of `adder'
+вам | to you
+сказал | he said
+ведь | particle `after all'
+там | there
+потом | then
+себя | oneself
+ничего | nothing
+ей | to her
+может | usually with `быть' as `maybe'
+они | they
+тут | here
+где | where
+есть | there is/are
+надо | got to, must
+ней | prepositional form of ей
+для | for
+мы | we
+тебя | thee
+их | them, their
+чем | than
+была | she was
+сам | self
+чтоб | in order to
+без | without
+будто | as if
+человек | man, person, one
+чего | genitive form of `what'
+раз | once
+тоже | also
+себе | to oneself
+под | beneath
+жизнь | life
+будет | will be
+ж | short form of intensifer particle `же'
+тогда | then
+кто | who
+этот | this
+говорил | was saying
+того | genitive form of `that'
+потому | for that reason
+этого | genitive form of `this'
+какой | which
+совсем | altogether
+ним | prepositional form of `его', `они'
+здесь | here
+этом | prepositional form of `этот'
+один | one
+почти | almost
+мой | my
+тем | instrumental/dative plural of `тот', `то'
+чтобы | full form of `in order that'
+нее | her (acc.)
+кажется | it seems
+сейчас | now
+были | they were
+куда | where to
+зачем | why
+сказать | to say
+всех | all (acc., gen. preposn. plural)
+никогда | never
+сегодня | today
+можно | possible, one can
+при | by
+наконец | finally
+два | two
+об | alternative form of `о', about
+другой | another
+хоть | even
+после | after
+над | above
+больше | more
+тот | that one (masc.)
+через | across, in
+эти | these
+нас | us
+про | about
+всего | in all, only, of all
+них | prepositional form of `они' (they)
+какая | which, feminine
+много | lots
+разве | interrogative particle
+сказала | she said
+три | three
+эту | this, acc. fem. sing.
+моя | my, feminine
+впрочем | moreover, besides
+хорошо | good
+свою | ones own, acc. fem. sing.
+этой | oblique form of `эта', fem. `this'
+перед | in front of
+иногда | sometimes
+лучше | better
+чуть | a little
+том | preposn. form of `that one'
+нельзя | one must not
+такой | such a one
+им | to them
+более | more
+всегда | always
+конечно | of course
+всю | acc. fem. sing of `all'
+между | between
+
+
+ | b: some paradigms
+ |
+ | personal pronouns
+ |
+ | я меня мне мной [мною]
+ | ты тебя тебе тобой [тобою]
+ | он его ему им [него, нему, ним]
+ | она ее эи ею [нее, нэи, нею]
+ | оно его ему им [него, нему, ним]
+ |
+ | мы нас нам нами
+ | вы вас вам вами
+ | они их им ими [них, ним, ними]
+ |
+ | себя себе собой [собою]
+ |
+ | demonstrative pronouns: этот (this), тот (that)
+ |
+ | этот эта это эти
+ | этого эты это эти
+ | этого этой этого этих
+ | этому этой этому этим
+ | этим этой этим [этою] этими
+ | этом этой этом этих
+ |
+ | тот та то те
+ | того ту то те
+ | того той того тех
+ | тому той тому тем
+ | тем той тем [тою] теми
+ | том той том тех
+ |
+ | determinative pronouns
+ |
+ | (a) весь (all)
+ |
+ | весь вся все все
+ | всего всю все все
+ | всего всей всего всех
+ | всему всей всему всем
+ | всем всей всем [всею] всеми
+ | всем всей всем всех
+ |
+ | (b) сам (himself etc)
+ |
+ | сам сама само сами
+ | самого саму само самих
+ | самого самой самого самих
+ | самому самой самому самим
+ | самим самой самим [самою] самими
+ | самом самой самом самих
+ |
+ | stems of verbs `to be', `to have', `to do' and modal
+ |
+ | быть бы буд быв есть суть
+ | име
+ | дел
+ | мог мож мочь
+ | уме
+ | хоч хот
+ | долж
+ | можн
+ | нужн
+ | нельзя
+
diff --git a/solr/conf/lang/stopwords_sv.txt b/solr/conf/lang/stopwords_sv.txt
new file mode 100644
index 000000000..096f87f67
--- /dev/null
+++ b/solr/conf/lang/stopwords_sv.txt
@@ -0,0 +1,133 @@
+ | From svn.tartarus.org/snowball/trunk/website/algorithms/swedish/stop.txt
+ | This file is distributed under the BSD License.
+ | See http://snowball.tartarus.org/license.php
+ | Also see http://www.opensource.org/licenses/bsd-license.html
+ | - Encoding was converted to UTF-8.
+ | - This notice was added.
+ |
+ | NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
+
+ | A Swedish stop word list. Comments begin with vertical bar. Each stop
+ | word is at the start of a line.
+
+ | This is a ranked list (commonest to rarest) of stopwords derived from
+ | a large text sample.
+
+ | Swedish stop words occasionally exhibit homonym clashes. For example
+ | så = so, but also seed. These are indicated clearly below.
+
+och | and
+det | it, this/that
+att | to (with infinitive)
+i | in, at
+en | a
+jag | I
+hon | she
+som | who, that
+han | he
+på | on
+den | it, this/that
+med | with
+var | where, each
+sig | him(self) etc
+för | for
+så | so (also: seed)
+till | to
+är | is
+men | but
+ett | a
+om | if; around, about
+hade | had
+de | they, these/those
+av | of
+icke | not, no
+mig | me
+du | you
+henne | her
+då | then, when
+sin | his
+nu | now
+har | have
+inte | inte någon = no one
+hans | his
+honom | him
+skulle | 'sake'
+hennes | her
+där | there
+min | my
+man | one (pronoun)
+ej | nor
+vid | at, by, on (also: vast)
+kunde | could
+något | some etc
+från | from, off
+ut | out
+när | when
+efter | after, behind
+upp | up
+vi | we
+dem | them
+vara | be
+vad | what
+över | over
+än | than
+dig | you
+kan | can
+sina | his
+här | here
+ha | have
+mot | towards
+alla | all
+under | under (also: wonder)
+någon | some etc
+eller | or (else)
+allt | all
+mycket | much
+sedan | since
+ju | why
+denna | this/that
+själv | myself, yourself etc
+detta | this/that
+åt | to
+utan | without
+varit | was
+hur | how
+ingen | no
+mitt | my
+ni | you
+bli | to be, become
+blev | from bli
+oss | us
+din | thy
+dessa | these/those
+några | some etc
+deras | their
+blir | from bli
+mina | my
+samma | (the) same
+vilken | who, that
+er | you, your
+sådan | such a
+vår | our
+blivit | from bli
+dess | its
+inom | within
+mellan | between
+sådant | such a
+varför | why
+varje | each
+vilka | who, that
+ditt | thy
+vem | who
+vilket | who, that
+sitta | his
+sådana | such a
+vart | each
+dina | thy
+vars | whose
+vårt | our
+våra | our
+ert | your
+era | your
+vilkas | whose
+
diff --git a/solr/conf/lang/stopwords_th.txt b/solr/conf/lang/stopwords_th.txt
new file mode 100644
index 000000000..07f0fabe6
--- /dev/null
+++ b/solr/conf/lang/stopwords_th.txt
@@ -0,0 +1,119 @@
+# Thai stopwords from:
+# "Opinion Detection in Thai Political News Columns
+# Based on Subjectivity Analysis"
+# Khampol Sukhum, Supot Nitsuwat, and Choochart Haruechaiyasak
+ไว้
+ไม่
+ไป
+ได้
+ให้
+ใน
+โดย
+แห่ง
+แล้ว
+และ
+แรก
+แบบ
+แต่
+เอง
+เห็น
+เลย
+เริ่ม
+เรา
+เมื่อ
+เพื่อ
+เพราะ
+เป็นการ
+เป็น
+เปิดเผย
+เปิด
+เนื่องจาก
+เดียวกัน
+เดียว
+เช่น
+เฉพาะ
+เคย
+เข้า
+เขา
+อีก
+อาจ
+อะไร
+ออก
+อย่าง
+อยู่
+อยาก
+หาก
+หลาย
+หลังจาก
+หลัง
+หรือ
+หนึ่ง
+ส่วน
+ส่ง
+สุด
+สําหรับ
+ว่า
+วัน
+ลง
+ร่วม
+ราย
+รับ
+ระหว่าง
+รวม
+ยัง
+มี
+มาก
+มา
+พร้อม
+พบ
+ผ่าน
+ผล
+บาง
+น่า
+นี้
+นํา
+นั้น
+นัก
+นอกจาก
+ทุก
+ที่สุด
+ที่
+ทําให้
+ทํา
+ทาง
+ทั้งนี้
+ทั้ง
+ถ้า
+ถูก
+ถึง
+ต้อง
+ต่างๆ
+ต่าง
+ต่อ
+ตาม
+ตั้งแต่
+ตั้ง
+ด้าน
+ด้วย
+ดัง
+ซึ่ง
+ช่วง
+จึง
+จาก
+จัด
+จะ
+คือ
+ความ
+ครั้ง
+คง
+ขึ้น
+ของ
+ขอ
+ขณะ
+ก่อน
+ก็
+การ
+กับ
+กัน
+กว่า
+กล่าว
diff --git a/solr/conf/lang/stopwords_tr.txt b/solr/conf/lang/stopwords_tr.txt
new file mode 100644
index 000000000..84d9408d4
--- /dev/null
+++ b/solr/conf/lang/stopwords_tr.txt
@@ -0,0 +1,212 @@
+# Turkish stopwords from LUCENE-559
+# merged with the list from "Information Retrieval on Turkish Texts"
+# (http://www.users.muohio.edu/canf/papers/JASIST2008offPrint.pdf)
+acaba
+altmış
+altı
+ama
+ancak
+arada
+aslında
+ayrıca
+bana
+bazı
+belki
+ben
+benden
+beni
+benim
+beri
+beş
+bile
+bin
+bir
+birçok
+biri
+birkaç
+birkez
+birşey
+birşeyi
+biz
+bize
+bizden
+bizi
+bizim
+böyle
+böylece
+bu
+buna
+bunda
+bundan
+bunlar
+bunları
+bunların
+bunu
+bunun
+burada
+çok
+çünkü
+da
+daha
+dahi
+de
+defa
+değil
+diğer
+diye
+doksan
+dokuz
+dolayı
+dolayısıyla
+dört
+edecek
+eden
+ederek
+edilecek
+ediliyor
+edilmesi
+ediyor
+eğer
+elli
+en
+etmesi
+etti
+ettiği
+ettiğini
+gibi
+göre
+halen
+hangi
+hatta
+hem
+henüz
+hep
+hepsi
+her
+herhangi
+herkesin
+hiç
+hiçbir
+için
+iki
+ile
+ilgili
+ise
+işte
+itibaren
+itibariyle
+kadar
+karşın
+katrilyon
+kendi
+kendilerine
+kendini
+kendisi
+kendisine
+kendisini
+kez
+ki
+kim
+kimden
+kime
+kimi
+kimse
+kırk
+milyar
+milyon
+mu
+mü
+mı
+nasıl
+ne
+neden
+nedenle
+nerde
+nerede
+nereye
+niye
+niçin
+o
+olan
+olarak
+oldu
+olduğu
+olduğunu
+olduklarını
+olmadı
+olmadığı
+olmak
+olması
+olmayan
+olmaz
+olsa
+olsun
+olup
+olur
+olursa
+oluyor
+on
+ona
+ondan
+onlar
+onlardan
+onları
+onların
+onu
+onun
+otuz
+oysa
+öyle
+pek
+rağmen
+sadece
+sanki
+sekiz
+seksen
+sen
+senden
+seni
+senin
+siz
+sizden
+sizi
+sizin
+şey
+şeyden
+şeyi
+şeyler
+şöyle
+şu
+şuna
+şunda
+şundan
+şunları
+şunu
+tarafından
+trilyon
+tüm
+üç
+üzere
+var
+vardı
+ve
+veya
+ya
+yani
+yapacak
+yapılan
+yapılması
+yapıyor
+yapmak
+yaptı
+yaptığı
+yaptığını
+yaptıkları
+yedi
+yerine
+yetmiş
+yine
+yirmi
+yoksa
+yüz
+zaten
diff --git a/solr/conf/lang/userdict_ja.txt b/solr/conf/lang/userdict_ja.txt
new file mode 100644
index 000000000..6f0368e4d
--- /dev/null
+++ b/solr/conf/lang/userdict_ja.txt
@@ -0,0 +1,29 @@
+#
+# This is a sample user dictionary for Kuromoji (JapaneseTokenizer)
+#
+# Add entries to this file in order to override the statistical model in terms
+# of segmentation, readings and part-of-speech tags. Notice that entries do
+# not have weights since they are always used when found. This is by-design
+# in order to maximize ease-of-use.
+#
+# Entries are defined using the following CSV format:
+# <text>,<token 1> ... <token n>,<reading 1> ... <reading n>,<part-of-speech tag>
+#
+# Notice that a single half-width space separates tokens and readings, and
+# that the number tokens and readings must match exactly.
+#
+# Also notice that multiple entries with the same <text> is undefined.
+#
+# Whitespace only lines are ignored. Comments are not allowed on entry lines.
+#
+
+# Custom segmentation for kanji compounds
+日本経済新聞,日本 経済 新聞,ニホン ケイザイ シンブン,カスタム名詞
+関西国際空港,関西 国際 空港,カンサイ コクサイ クウコウ,カスタム名詞
+
+# Custom segmentation for compound katakana
+トートバッグ,トート バッグ,トート バッグ,かずカナ名詞
+ショルダーバッグ,ショルダー バッグ,ショルダー バッグ,かずカナ名詞
+
+# Custom reading for former sumo wrestler
+朝青龍,朝青龍,アサショウリュウ,カスタム人名
diff --git a/solr/conf/params.json b/solr/conf/params.json
new file mode 100644
index 000000000..06114ef25
--- /dev/null
+++ b/solr/conf/params.json
@@ -0,0 +1,20 @@
+{"params":{
+ "query":{
+ "defType":"edismax",
+ "q.alt":"*:*",
+ "rows":"10",
+ "fl":"*,score",
+ "":{"v":0}
+ },
+ "facets":{
+ "facet":"on",
+ "facet.mincount": "1",
+ "":{"v":0}
+ },
+ "velocity":{
+ "wt": "velocity",
+ "v.template":"browse",
+ "v.layout": "layout",
+ "":{"v":0}
+ }
+}} \ No newline at end of file
diff --git a/solr/conf/protwords.txt b/solr/conf/protwords.txt
new file mode 100644
index 000000000..1dfc0abec
--- /dev/null
+++ b/solr/conf/protwords.txt
@@ -0,0 +1,21 @@
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#-----------------------------------------------------------------------
+# Use a protected word file to protect against the stemmer reducing two
+# unrelated words to the same base word.
+
+# Some non-words that normally won't be encountered,
+# just to test that they won't be stemmed.
+dontstems
+zwhacky
+
diff --git a/solr/conf/schema.xml b/solr/conf/schema.xml
new file mode 100644
index 000000000..9217e015b
--- /dev/null
+++ b/solr/conf/schema.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema name="default-config" version="1.6">
+ <uniqueKey>id</uniqueKey>
+ <fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true"/>
+ <fieldType name="text" class="solr.TextField">
+ <analyzer type="index">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" words="stopwords.txt"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.PorterStemFilterFactory"/>
+ <filter class="solr.EdgeNGramFilterFactory" minGramSize="3" maxGramSize="12"/>
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" words="stopwords.txt"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.PorterStemFilterFactory"/>
+ </analyzer>
+ </fieldType>
+ <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100" multiValued="true">
+ <analyzer type="index">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
+ <!-- in this example, we will only use synonyms at query time
+ <filter class="solr.SynonymGraphFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
+ <filter class="solr.FlattenGraphFilterFactory"/>
+ -->
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
+ <filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ </analyzer>
+ </fieldType>
+
+ <fieldType name="plong" class="solr.LongPointField"/>
+ <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
+ <fieldType name="pdate" class="solr.DatePointField"/>
+ <fieldType name="pdaterange" class="solr.DateRangeField"/>
+ <fieldType name="pdouble" class="solr.DoublePointField"/>
+
+ <field name="text" type="text" indexed="true" stored="false" uninvertible="false" multiValued="true"/>
+ <field name="id" type="string" indexed="true" stored="true" uninvertible="false" required="true"/>
+ <field name="_version_" type="plong" indexed="true" stored="true"/>
+
+ <dynamicField name="*_t" type="text" indexed="true" stored="true" uninvertible="false" docValues="false"/>
+ <dynamicField name="*_n" type="pdouble" indexed="true" stored="true" uninvertible="false" docValues="false"/>
+ <dynamicField name="*_d" type="pdaterange" indexed="true" stored="true" uninvertible="false" docValues="false"/>
+ <dynamicField name="*_l" type="string" indexed="true" stored="true" uninvertible="false" docValues="false" multiValued="true"/>
+ <dynamicField name="*_i" type="string" indexed="true" stored="true" uninvertible="false" docValues="false"/>
+ <dynamicField name="*_b" type="boolean" indexed="true" stored="true" uninvertible="false" docValues="false"/>
+
+ <dynamicField name="*_a" type="text" indexed="true" stored="false" uninvertible="false" docValues="false"/>
+
+ <copyField source="*_t" dest="text"/>
+
+ <copyField source="*_t" dest="*_a"/>
+ <copyField source="*_n" dest="*_a"/>
+</schema>
diff --git a/solr/conf/schema.xml~ b/solr/conf/schema.xml~
new file mode 100644
index 000000000..3b2addf78
--- /dev/null
+++ b/solr/conf/schema.xml~
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema name="default-config" version="1.6">
+ <uniqueKey>id</uniqueKey>
+ <fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true"/>
+ <fieldType name="text" class="solr.TextField">
+ <analyzer type="index">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" words="stopwords.txt"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.PorterStemFilterFactory"/>
+ <filter class="solr.EdgeNGramFilterFactory" minGramSize="3" maxGramSize="12"/>
+ </analyzer>
+ <analyzer type="query">
+ <tokenizer class="solr.StandardTokenizerFactory"/>
+ <filter class="solr.StopFilterFactory" words="stopwords.txt"/>
+ <filter class="solr.LowerCaseFilterFactory"/>
+ <filter class="solr.PorterStemFilterFactory"/>
+ </analyzer>
+ </fieldType>
+ <field name="data" type="text" indexed="true" stored="true" uninvertible="false"/>
+ <field name="id" type="string" indexed="true" stored="true" uninvertible="false" required="true"/>
+ <field name="_version_" type="long" indexed="false" stored="false" multiValued="false"/>
+</schema>
diff --git a/solr/conf/solrconfig.xml b/solr/conf/solrconfig.xml
new file mode 100644
index 000000000..90eff5363
--- /dev/null
+++ b/solr/conf/solrconfig.xml
@@ -0,0 +1,1328 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ For more details about configurations options that may appear in
+ this file, see http://wiki.apache.org/solr/SolrConfigXml.
+-->
+<config>
+ <!-- In all configuration below, a prefix of "solr." for class names
+ is an alias that causes solr to search appropriate packages,
+ including org.apache.solr.(search|update|request|core|analysis)
+
+ You may also specify a fully qualified Java classname if you
+ have your own custom plugins.
+ -->
+
+ <!-- Controls what version of Lucene various components of Solr
+ adhere to. Generally, you want to use the latest version to
+ get all bug fixes and improvements. It is highly recommended
+ that you fully re-index after changing this setting as it can
+ affect both how text is indexed and queried.
+ -->
+ <luceneMatchVersion>8.0.0</luceneMatchVersion>
+
+ <!-- <lib/> directives can be used to instruct Solr to load any Jars
+ identified and use them to resolve any "plugins" specified in
+ your solrconfig.xml or schema.xml (ie: Analyzers, Request
+ Handlers, etc...).
+
+ All directories and paths are resolved relative to the
+ instanceDir.
+
+ Please note that <lib/> directives are processed in the order
+ that they appear in your solrconfig.xml file, and are "stacked"
+ on top of each other when building a ClassLoader - so if you have
+ plugin jars with dependencies on other jars, the "lower level"
+ dependency jars should be loaded first.
+
+ If a "./lib" directory exists in your instanceDir, all files
+ found in it are included as if you had used the following
+ syntax...
+
+ <lib dir="./lib" />
+ -->
+
+ <!-- A 'dir' option by itself adds any files found in the directory
+ to the classpath, this is useful for including all jars in a
+ directory.
+
+ When a 'regex' is specified in addition to a 'dir', only the
+ files in that directory which completely match the regex
+ (anchored on both ends) will be included.
+
+ If a 'dir' option (with or without a regex) is used and nothing
+ is found that matches, a warning will be logged.
+
+ The examples below can be used to load some solr-contribs along
+ with their external dependencies.
+ -->
+ <lib dir="${solr.install.dir:../../../..}/contrib/extraction/lib" regex=".*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-cell-\d.*\.jar" />
+
+ <lib dir="${solr.install.dir:../../../..}/contrib/clustering/lib/" regex=".*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-clustering-\d.*\.jar" />
+
+ <lib dir="${solr.install.dir:../../../..}/contrib/langid/lib/" regex=".*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-langid-\d.*\.jar" />
+
+ <lib dir="${solr.install.dir:../../../..}/contrib/velocity/lib" regex=".*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-velocity-\d.*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-ltr-\d.*\.jar" />
+
+ <!-- an exact 'path' can be used instead of a 'dir' to specify a
+ specific jar file. This will cause a serious error to be logged
+ if it can't be loaded.
+ -->
+ <!--
+ <lib path="../a-jar-that-does-not-exist.jar" />
+ -->
+
+ <!-- Data Directory
+
+ Used to specify an alternate directory to hold all index data
+ other than the default ./data under the Solr home. If
+ replication is in use, this should match the replication
+ configuration.
+ -->
+ <dataDir>${solr.data.dir:}</dataDir>
+
+
+ <!-- The DirectoryFactory to use for indexes.
+
+ solr.StandardDirectoryFactory is filesystem
+ based and tries to pick the best implementation for the current
+ JVM and platform. solr.NRTCachingDirectoryFactory, the default,
+ wraps solr.StandardDirectoryFactory and caches small files in memory
+ for better NRT performance.
+
+ One can force a particular implementation via solr.MMapDirectoryFactory,
+ solr.NIOFSDirectoryFactory, or solr.SimpleFSDirectoryFactory.
+
+ solr.RAMDirectoryFactory is memory based and not persistent.
+ -->
+ <directoryFactory name="DirectoryFactory"
+ class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
+
+ <schemaFactory class="ClassicIndexSchemaFactory"/>
+ <!-- The CodecFactory for defining the format of the inverted index.
+ The default implementation is SchemaCodecFactory, which is the official Lucene
+ index format, but hooks into the schema to provide per-field customization of
+ the postings lists and per-document values in the fieldType element
+ (postingsFormat/docValuesFormat). Note that most of the alternative implementations
+ are experimental, so if you choose to customize the index format, it's a good
+ idea to convert back to the official format e.g. via IndexWriter.addIndexes(IndexReader)
+ before upgrading to a newer version to avoid unnecessary reindexing.
+ A "compressionMode" string element can be added to <codecFactory> to choose
+ between the existing compression modes in the default codec: "BEST_SPEED" (default)
+ or "BEST_COMPRESSION".
+ -->
+ <codecFactory class="solr.SchemaCodecFactory"/>
+
+ <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Index Config - These settings control low-level behavior of indexing
+ Most example settings here show the default value, but are commented
+ out, to more easily see where customizations have been made.
+
+ Note: This replaces <indexDefaults> and <mainIndex> from older versions
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
+ <indexConfig>
+ <!-- maxFieldLength was removed in 4.0. To get similar behavior, include a
+ LimitTokenCountFilterFactory in your fieldType definition. E.g.
+ <filter class="solr.LimitTokenCountFilterFactory" maxTokenCount="10000"/>
+ -->
+ <!-- Maximum time to wait for a write lock (ms) for an IndexWriter. Default: 1000 -->
+ <!-- <writeLockTimeout>1000</writeLockTimeout> -->
+
+ <!-- Expert: Enabling compound file will use less files for the index,
+ using fewer file descriptors on the expense of performance decrease.
+ Default in Lucene is "true". Default in Solr is "false" (since 3.6) -->
+ <!-- <useCompoundFile>false</useCompoundFile> -->
+
+ <!-- ramBufferSizeMB sets the amount of RAM that may be used by Lucene
+ indexing for buffering added documents and deletions before they are
+ flushed to the Directory.
+ maxBufferedDocs sets a limit on the number of documents buffered
+ before flushing.
+ If both ramBufferSizeMB and maxBufferedDocs is set, then
+ Lucene will flush based on whichever limit is hit first. -->
+ <!-- <ramBufferSizeMB>100</ramBufferSizeMB> -->
+ <!-- <maxBufferedDocs>1000</maxBufferedDocs> -->
+
+ <!-- Expert: Merge Policy
+ The Merge Policy in Lucene controls how merging of segments is done.
+ The default since Solr/Lucene 3.3 is TieredMergePolicy.
+ The default since Lucene 2.3 was the LogByteSizeMergePolicy,
+ Even older versions of Lucene used LogDocMergePolicy.
+ -->
+ <!--
+ <mergePolicyFactory class="org.apache.solr.index.TieredMergePolicyFactory">
+ <int name="maxMergeAtOnce">10</int>
+ <int name="segmentsPerTier">10</int>
+ <double name="noCFSRatio">0.1</double>
+ </mergePolicyFactory>
+ -->
+
+ <!-- Expert: Merge Scheduler
+ The Merge Scheduler in Lucene controls how merges are
+ performed. The ConcurrentMergeScheduler (Lucene 2.3 default)
+ can perform merges in the background using separate threads.
+ The SerialMergeScheduler (Lucene 2.2 default) does not.
+ -->
+ <!--
+ <mergeScheduler class="org.apache.lucene.index.ConcurrentMergeScheduler"/>
+ -->
+
+ <!-- LockFactory
+
+ This option specifies which Lucene LockFactory implementation
+ to use.
+
+ single = SingleInstanceLockFactory - suggested for a
+ read-only index or when there is no possibility of
+ another process trying to modify the index.
+ native = NativeFSLockFactory - uses OS native file locking.
+ Do not use when multiple solr webapps in the same
+ JVM are attempting to share a single index.
+ simple = SimpleFSLockFactory - uses a plain file for locking
+
+ Defaults: 'native' is default for Solr3.6 and later, otherwise
+ 'simple' is the default
+
+ More details on the nuances of each LockFactory...
+ http://wiki.apache.org/lucene-java/AvailableLockFactories
+ -->
+ <lockType>${solr.lock.type:native}</lockType>
+
+ <!-- Commit Deletion Policy
+ Custom deletion policies can be specified here. The class must
+ implement org.apache.lucene.index.IndexDeletionPolicy.
+
+ The default Solr IndexDeletionPolicy implementation supports
+ deleting index commit points on number of commits, age of
+ commit point and optimized status.
+
+ The latest commit point should always be preserved regardless
+ of the criteria.
+ -->
+ <!--
+ <deletionPolicy class="solr.SolrDeletionPolicy">
+ -->
+ <!-- The number of commit points to be kept -->
+ <!-- <str name="maxCommitsToKeep">1</str> -->
+ <!-- The number of optimized commit points to be kept -->
+ <!-- <str name="maxOptimizedCommitsToKeep">0</str> -->
+ <!--
+ Delete all commit points once they have reached the given age.
+ Supports DateMathParser syntax e.g.
+ -->
+ <!--
+ <str name="maxCommitAge">30MINUTES</str>
+ <str name="maxCommitAge">1DAY</str>
+ -->
+ <!--
+ </deletionPolicy>
+ -->
+
+ <!-- Lucene Infostream
+
+ To aid in advanced debugging, Lucene provides an "InfoStream"
+ of detailed information when indexing.
+
+ Setting The value to true will instruct the underlying Lucene
+ IndexWriter to write its debugging info the specified file
+ -->
+ <!-- <infoStream file="INFOSTREAM.txt">false</infoStream> -->
+ </indexConfig>
+
+
+ <!-- JMX
+
+ This example enables JMX if and only if an existing MBeanServer
+ is found, use this if you want to configure JMX through JVM
+ parameters. Remove this to disable exposing Solr configuration
+ and statistics to JMX.
+
+ For more details see http://wiki.apache.org/solr/SolrJmx
+ -->
+ <jmx />
+ <!-- If you want to connect to a particular server, specify the
+ agentId
+ -->
+ <!-- <jmx agentId="myAgent" /> -->
+ <!-- If you want to start a new MBeanServer, specify the serviceUrl -->
+ <!-- <jmx serviceUrl="service:jmx:rmi:///jndi/rmi://localhost:9999/solr"/>
+ -->
+
+ <!-- The default high-performance update handler -->
+ <updateHandler class="solr.DirectUpdateHandler2">
+
+ <!-- Enables a transaction log, used for real-time get, durability, and
+ and solr cloud replica recovery. The log can grow as big as
+ uncommitted changes to the index, so use of a hard autoCommit
+ is recommended (see below).
+ "dir" - the target directory for transaction logs, defaults to the
+ solr data directory.
+ "numVersionBuckets" - sets the number of buckets used to keep
+ track of max version values when checking for re-ordered
+ updates; increase this value to reduce the cost of
+ synchronizing access to version buckets during high-volume
+ indexing, this requires 8 bytes (long) * numVersionBuckets
+ of heap space per Solr core.
+ -->
+ <updateLog>
+ <str name="dir">${solr.ulog.dir:}</str>
+ <int name="numVersionBuckets">${solr.ulog.numVersionBuckets:65536}</int>
+ </updateLog>
+
+ <!-- AutoCommit
+
+ Perform a hard commit automatically under certain conditions.
+ Instead of enabling autoCommit, consider using "commitWithin"
+ when adding documents.
+
+ http://wiki.apache.org/solr/UpdateXmlMessages
+
+ maxDocs - Maximum number of documents to add since the last
+ commit before automatically triggering a new commit.
+
+ maxTime - Maximum amount of time in ms that is allowed to pass
+ since a document was added before automatically
+ triggering a new commit.
+ openSearcher - if false, the commit causes recent index changes
+ to be flushed to stable storage, but does not cause a new
+ searcher to be opened to make those changes visible.
+
+ If the updateLog is enabled, then it's highly recommended to
+ have some sort of hard autoCommit to limit the log size.
+ -->
+ <autoCommit>
+ <maxTime>600000</maxTime>
+ <openSearcher>false</openSearcher>
+ </autoCommit>
+
+ <!-- softAutoCommit is like autoCommit except it causes a
+ 'soft' commit which only ensures that changes are visible
+ but does not ensure that data is synced to disk. This is
+ faster and more near-realtime friendly than a hard commit.
+ -->
+
+ <autoSoftCommit>
+ <maxTime>1000</maxTime>
+ </autoSoftCommit>
+
+ <!-- Update Related Event Listeners
+
+ Various IndexWriter related events can trigger Listeners to
+ take actions.
+
+ postCommit - fired after every commit or optimize command
+ postOptimize - fired after every optimize command
+ -->
+
+ </updateHandler>
+
+ <!-- IndexReaderFactory
+
+ Use the following format to specify a custom IndexReaderFactory,
+ which allows for alternate IndexReader implementations.
+
+ ** Experimental Feature **
+
+ Please note - Using a custom IndexReaderFactory may prevent
+ certain other features from working. The API to
+ IndexReaderFactory may change without warning or may even be
+ removed from future releases if the problems cannot be
+ resolved.
+
+
+ ** Features that may not work with custom IndexReaderFactory **
+
+ The ReplicationHandler assumes a disk-resident index. Using a
+ custom IndexReader implementation may cause incompatibility
+ with ReplicationHandler and may cause replication to not work
+ correctly. See SOLR-1366 for details.
+
+ -->
+ <!--
+ <indexReaderFactory name="IndexReaderFactory" class="package.class">
+ <str name="someArg">Some Value</str>
+ </indexReaderFactory >
+ -->
+
+ <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Query section - these settings control query time things like caches
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
+ <query>
+
+ <!-- Maximum number of clauses in each BooleanQuery, an exception
+ is thrown if exceeded. It is safe to increase or remove this setting,
+ since it is purely an arbitrary limit to try and catch user errors where
+ large boolean queries may not be the best implementation choice.
+ -->
+ <maxBooleanClauses>${solr.max.booleanClauses:1024}</maxBooleanClauses>
+
+ <!-- Solr Internal Query Caches
+
+ There are two implementations of cache available for Solr,
+ LRUCache, based on a synchronized LinkedHashMap, and
+ FastLRUCache, based on a ConcurrentHashMap.
+
+ FastLRUCache has faster gets and slower puts in single
+ threaded operation and thus is generally faster than LRUCache
+ when the hit ratio of the cache is high (> 75%), and may be
+ faster under other scenarios on multi-cpu systems.
+ -->
+
+ <!-- Filter Cache
+
+ Cache used by SolrIndexSearcher for filters (DocSets),
+ unordered sets of *all* documents that match a query. When a
+ new searcher is opened, its caches may be prepopulated or
+ "autowarmed" using data from caches in the old searcher.
+ autowarmCount is the number of items to prepopulate. For
+ LRUCache, the autowarmed items will be the most recently
+ accessed items.
+
+ Parameters:
+ class - the SolrCache implementation LRUCache or
+ (LRUCache or FastLRUCache)
+ size - the maximum number of entries in the cache
+ initialSize - the initial capacity (number of entries) of
+ the cache. (see java.util.HashMap)
+ autowarmCount - the number of entries to prepopulate from
+ and old cache.
+ maxRamMB - the maximum amount of RAM (in MB) that this cache is allowed
+ to occupy. Note that when this option is specified, the size
+ and initialSize parameters are ignored.
+ -->
+ <filterCache class="solr.FastLRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="0"/>
+
+ <!-- Query Result Cache
+
+ Caches results of searches - ordered lists of document ids
+ (DocList) based on a query, a sort, and the range of documents requested.
+ Additional supported parameter by LRUCache:
+ maxRamMB - the maximum amount of RAM (in MB) that this cache is allowed
+ to occupy
+ -->
+ <queryResultCache class="solr.LRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="0"/>
+
+ <!-- Document Cache
+
+ Caches Lucene Document objects (the stored fields for each
+ document). Since Lucene internal document ids are transient,
+ this cache will not be autowarmed.
+ -->
+ <documentCache class="solr.LRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="0"/>
+
+ <!-- custom cache currently used by block join -->
+ <cache name="perSegFilter"
+ class="solr.search.LRUCache"
+ size="10"
+ initialSize="0"
+ autowarmCount="10"
+ regenerator="solr.NoOpRegenerator" />
+
+ <!-- Field Value Cache
+
+ Cache used to hold field values that are quickly accessible
+ by document id. The fieldValueCache is created by default
+ even if not configured here.
+ -->
+ <!--
+ <fieldValueCache class="solr.FastLRUCache"
+ size="512"
+ autowarmCount="128"
+ showItems="32" />
+ -->
+
+ <!-- Custom Cache
+
+ Example of a generic cache. These caches may be accessed by
+ name through SolrIndexSearcher.getCache(),cacheLookup(), and
+ cacheInsert(). The purpose is to enable easy caching of
+ user/application level data. The regenerator argument should
+ be specified as an implementation of solr.CacheRegenerator
+ if autowarming is desired.
+ -->
+ <!--
+ <cache name="myUserCache"
+ class="solr.LRUCache"
+ size="4096"
+ initialSize="1024"
+ autowarmCount="1024"
+ regenerator="com.mycompany.MyRegenerator"
+ />
+ -->
+
+
+ <!-- Lazy Field Loading
+
+ If true, stored fields that are not requested will be loaded
+ lazily. This can result in a significant speed improvement
+ if the usual case is to not load all stored fields,
+ especially if the skipped fields are large compressed text
+ fields.
+ -->
+ <enableLazyFieldLoading>true</enableLazyFieldLoading>
+
+ <!-- Use Filter For Sorted Query
+
+ A possible optimization that attempts to use a filter to
+ satisfy a search. If the requested sort does not include
+ score, then the filterCache will be checked for a filter
+ matching the query. If found, the filter will be used as the
+ source of document ids, and then the sort will be applied to
+ that.
+
+ For most situations, this will not be useful unless you
+ frequently get the same search repeatedly with different sort
+ options, and none of them ever use "score"
+ -->
+ <!--
+ <useFilterForSortedQuery>true</useFilterForSortedQuery>
+ -->
+
+ <!-- Result Window Size
+
+ An optimization for use with the queryResultCache. When a search
+ is requested, a superset of the requested number of document ids
+ are collected. For example, if a search for a particular query
+ requests matching documents 10 through 19, and queryWindowSize is 50,
+ then documents 0 through 49 will be collected and cached. Any further
+ requests in that range can be satisfied via the cache.
+ -->
+ <queryResultWindowSize>20</queryResultWindowSize>
+
+ <!-- Maximum number of documents to cache for any entry in the
+ queryResultCache.
+ -->
+ <queryResultMaxDocsCached>200</queryResultMaxDocsCached>
+
+ <!-- Query Related Event Listeners
+
+ Various IndexSearcher related events can trigger Listeners to
+ take actions.
+
+ newSearcher - fired whenever a new searcher is being prepared
+ and there is a current searcher handling requests (aka
+ registered). It can be used to prime certain caches to
+ prevent long request times for certain requests.
+
+ firstSearcher - fired whenever a new searcher is being
+ prepared but there is no current registered searcher to handle
+ requests or to gain autowarming data from.
+
+
+ -->
+ <!-- QuerySenderListener takes an array of NamedList and executes a
+ local query request for each NamedList in sequence.
+ -->
+ <listener event="newSearcher" class="solr.QuerySenderListener">
+ <arr name="queries">
+ <!--
+ <lst><str name="q">solr</str><str name="sort">price asc</str></lst>
+ <lst><str name="q">rocks</str><str name="sort">weight asc</str></lst>
+ -->
+ </arr>
+ </listener>
+ <listener event="firstSearcher" class="solr.QuerySenderListener">
+ <arr name="queries">
+ <!--
+ <lst>
+ <str name="q">static firstSearcher warming in solrconfig.xml</str>
+ </lst>
+ -->
+ </arr>
+ </listener>
+
+ <!-- Use Cold Searcher
+
+ If a search request comes in and there is no current
+ registered searcher, then immediately register the still
+ warming searcher and use it. If "false" then all requests
+ will block until the first searcher is done warming.
+ -->
+ <useColdSearcher>false</useColdSearcher>
+
+ </query>
+
+
+ <!-- Request Dispatcher
+
+ This section contains instructions for how the SolrDispatchFilter
+ should behave when processing requests for this SolrCore.
+
+ -->
+ <requestDispatcher>
+ <!-- Request Parsing
+
+ These settings indicate how Solr Requests may be parsed, and
+ what restrictions may be placed on the ContentStreams from
+ those requests
+
+ enableRemoteStreaming - enables use of the stream.file
+ and stream.url parameters for specifying remote streams.
+
+ multipartUploadLimitInKB - specifies the max size (in KiB) of
+ Multipart File Uploads that Solr will allow in a Request.
+
+ formdataUploadLimitInKB - specifies the max size (in KiB) of
+ form data (application/x-www-form-urlencoded) sent via
+ POST. You can use POST to pass request parameters not
+ fitting into the URL.
+
+ addHttpRequestToContext - if set to true, it will instruct
+ the requestParsers to include the original HttpServletRequest
+ object in the context map of the SolrQueryRequest under the
+ key "httpRequest". It will not be used by any of the existing
+ Solr components, but may be useful when developing custom
+ plugins.
+
+ *** WARNING ***
+ Before enabling remote streaming, you should make sure your
+ system has authentication enabled.
+
+ <requestParsers enableRemoteStreaming="false"
+ multipartUploadLimitInKB="-1"
+ formdataUploadLimitInKB="-1"
+ addHttpRequestToContext="false"/>
+ -->
+
+ <!-- HTTP Caching
+
+ Set HTTP caching related parameters (for proxy caches and clients).
+
+ The options below instruct Solr not to output any HTTP Caching
+ related headers
+ -->
+ <httpCaching never304="true" />
+ <!-- If you include a <cacheControl> directive, it will be used to
+ generate a Cache-Control header (as well as an Expires header
+ if the value contains "max-age=")
+
+ By default, no Cache-Control header is generated.
+
+ You can use the <cacheControl> option even if you have set
+ never304="true"
+ -->
+ <!--
+ <httpCaching never304="true" >
+ <cacheControl>max-age=30, public</cacheControl>
+ </httpCaching>
+ -->
+ <!-- To enable Solr to respond with automatically generated HTTP
+ Caching headers, and to response to Cache Validation requests
+ correctly, set the value of never304="false"
+
+ This will cause Solr to generate Last-Modified and ETag
+ headers based on the properties of the Index.
+
+ The following options can also be specified to affect the
+ values of these headers...
+
+ lastModFrom - the default value is "openTime" which means the
+ Last-Modified value (and validation against If-Modified-Since
+ requests) will all be relative to when the current Searcher
+ was opened. You can change it to lastModFrom="dirLastMod" if
+ you want the value to exactly correspond to when the physical
+ index was last modified.
+
+ etagSeed="..." is an option you can change to force the ETag
+ header (and validation against If-None-Match requests) to be
+ different even if the index has not changed (ie: when making
+ significant changes to your config file)
+
+ (lastModifiedFrom and etagSeed are both ignored if you use
+ the never304="true" option)
+ -->
+ <!--
+ <httpCaching lastModifiedFrom="openTime"
+ etagSeed="Solr">
+ <cacheControl>max-age=30, public</cacheControl>
+ </httpCaching>
+ -->
+ </requestDispatcher>
+
+ <!-- Request Handlers
+
+ http://wiki.apache.org/solr/SolrRequestHandler
+
+ Incoming queries will be dispatched to a specific handler by name
+ based on the path specified in the request.
+
+ If a Request Handler is declared with startup="lazy", then it will
+ not be initialized until the first request that uses it.
+
+ -->
+ <!-- SearchHandler
+
+ http://wiki.apache.org/solr/SearchHandler
+
+ For processing Search Queries, the primary Request Handler
+ provided with Solr is "SearchHandler" It delegates to a sequent
+ of SearchComponents (see below) and supports distributed
+ queries across multiple shards
+ -->
+ <requestHandler name="/select" class="solr.SearchHandler">
+ <!-- default values for query parameters can be specified, these
+ will be overridden by parameters in the request
+ -->
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ <int name="rows">10</int>
+ <str name="df">text</str>
+ <!-- Default search field
+ <str name="df">text</str>
+ -->
+ <!-- Change from JSON to XML format (the default prior to Solr 7.0)
+ <str name="wt">xml</str>
+ -->
+ </lst>
+ <!-- In addition to defaults, "appends" params can be specified
+ to identify values which should be appended to the list of
+ multi-val params from the query (or the existing "defaults").
+ -->
+ <!-- In this example, the param "fq=instock:true" would be appended to
+ any query time fq params the user may specify, as a mechanism for
+ partitioning the index, independent of any user selected filtering
+ that may also be desired (perhaps as a result of faceted searching).
+
+ NOTE: there is *absolutely* nothing a client can do to prevent these
+ "appends" values from being used, so don't use this mechanism
+ unless you are sure you always want it.
+ -->
+ <!--
+ <lst name="appends">
+ <str name="fq">inStock:true</str>
+ </lst>
+ -->
+ <!-- "invariants" are a way of letting the Solr maintainer lock down
+ the options available to Solr clients. Any params values
+ specified here are used regardless of what values may be specified
+ in either the query, the "defaults", or the "appends" params.
+
+ In this example, the facet.field and facet.query params would
+ be fixed, limiting the facets clients can use. Faceting is
+ not turned on by default - but if the client does specify
+ facet=true in the request, these are the only facets they
+ will be able to see counts for; regardless of what other
+ facet.field or facet.query params they may specify.
+
+ NOTE: there is *absolutely* nothing a client can do to prevent these
+ "invariants" values from being used, so don't use this mechanism
+ unless you are sure you always want it.
+ -->
+ <!--
+ <lst name="invariants">
+ <str name="facet.field">cat</str>
+ <str name="facet.field">manu_exact</str>
+ <str name="facet.query">price:[* TO 500]</str>
+ <str name="facet.query">price:[500 TO *]</str>
+ </lst>
+ -->
+ <!-- If the default list of SearchComponents is not desired, that
+ list can either be overridden completely, or components can be
+ prepended or appended to the default list. (see below)
+ -->
+ <!--
+ <arr name="components">
+ <str>nameOfCustomComponent1</str>
+ <str>nameOfCustomComponent2</str>
+ </arr>
+ -->
+ </requestHandler>
+
+ <!-- A request handler that returns indented JSON by default -->
+ <requestHandler name="/query" class="solr.SearchHandler">
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ <str name="wt">json</str>
+ <str name="indent">true</str>
+ </lst>
+ </requestHandler>
+
+
+ <!-- A Robust Example
+
+ This example SearchHandler declaration shows off usage of the
+ SearchHandler with many defaults declared
+
+ Note that multiple instances of the same Request Handler
+ (SearchHandler) can be registered multiple times with different
+ names (and different init parameters)
+ -->
+ <requestHandler name="/browse" class="solr.SearchHandler" useParams="query,facets,velocity,browse">
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ </lst>
+ </requestHandler>
+
+ <initParams path="/update/**,/query,/select,/tvrh,/elevate,/spell,/browse">
+ <lst name="defaults">
+ <str name="df">_text_</str>
+ </lst>
+ </initParams>
+
+ <!-- Solr Cell Update Request Handler
+
+ http://wiki.apache.org/solr/ExtractingRequestHandler
+
+ -->
+ <requestHandler name="/update/extract"
+ startup="lazy"
+ class="solr.extraction.ExtractingRequestHandler" >
+ <lst name="defaults">
+ <str name="lowernames">true</str>
+ <str name="fmap.content">_text_</str>
+ </lst>
+ </requestHandler>
+
+ <!-- Search Components
+
+ Search components are registered to SolrCore and used by
+ instances of SearchHandler (which can access them by name)
+
+ By default, the following components are available:
+
+ <searchComponent name="query" class="solr.QueryComponent" />
+ <searchComponent name="facet" class="solr.FacetComponent" />
+ <searchComponent name="mlt" class="solr.MoreLikeThisComponent" />
+ <searchComponent name="highlight" class="solr.HighlightComponent" />
+ <searchComponent name="stats" class="solr.StatsComponent" />
+ <searchComponent name="debug" class="solr.DebugComponent" />
+
+ Default configuration in a requestHandler would look like:
+
+ <arr name="components">
+ <str>query</str>
+ <str>facet</str>
+ <str>mlt</str>
+ <str>highlight</str>
+ <str>stats</str>
+ <str>debug</str>
+ </arr>
+
+ If you register a searchComponent to one of the standard names,
+ that will be used instead of the default.
+
+ To insert components before or after the 'standard' components, use:
+
+ <arr name="first-components">
+ <str>myFirstComponentName</str>
+ </arr>
+
+ <arr name="last-components">
+ <str>myLastComponentName</str>
+ </arr>
+
+ NOTE: The component registered with the name "debug" will
+ always be executed after the "last-components"
+
+ -->
+
+ <!-- Spell Check
+
+ The spell check component can return a list of alternative spelling
+ suggestions.
+
+ http://wiki.apache.org/solr/SpellCheckComponent
+ -->
+ <searchComponent name="spellcheck" class="solr.SpellCheckComponent">
+
+ <str name="queryAnalyzerFieldType">text_general</str>
+
+ <!-- Multiple "Spell Checkers" can be declared and used by this
+ component
+ -->
+
+ <!-- a spellchecker built from a field of the main index -->
+ <lst name="spellchecker">
+ <str name="name">default</str>
+ <str name="field">_text_</str>
+ <str name="classname">solr.DirectSolrSpellChecker</str>
+ <!-- the spellcheck distance measure used, the default is the internal levenshtein -->
+ <str name="distanceMeasure">internal</str>
+ <!-- minimum accuracy needed to be considered a valid spellcheck suggestion -->
+ <float name="accuracy">0.5</float>
+ <!-- the maximum #edits we consider when enumerating terms: can be 1 or 2 -->
+ <int name="maxEdits">2</int>
+ <!-- the minimum shared prefix when enumerating terms -->
+ <int name="minPrefix">1</int>
+ <!-- maximum number of inspections per result. -->
+ <int name="maxInspections">5</int>
+ <!-- minimum length of a query term to be considered for correction -->
+ <int name="minQueryLength">4</int>
+ <!-- maximum threshold of documents a query term can appear to be considered for correction -->
+ <float name="maxQueryFrequency">0.01</float>
+ <!-- uncomment this to require suggestions to occur in 1% of the documents
+ <float name="thresholdTokenFrequency">.01</float>
+ -->
+ </lst>
+
+ <!-- a spellchecker that can break or combine words. See "/spell" handler below for usage -->
+ <!--
+ <lst name="spellchecker">
+ <str name="name">wordbreak</str>
+ <str name="classname">solr.WordBreakSolrSpellChecker</str>
+ <str name="field">name</str>
+ <str name="combineWords">true</str>
+ <str name="breakWords">true</str>
+ <int name="maxChanges">10</int>
+ </lst>
+ -->
+ </searchComponent>
+
+ <!-- A request handler for demonstrating the spellcheck component.
+
+ NOTE: This is purely as an example. The whole purpose of the
+ SpellCheckComponent is to hook it into the request handler that
+ handles your normal user queries so that a separate request is
+ not needed to get suggestions.
+
+ IN OTHER WORDS, THERE IS REALLY GOOD CHANCE THE SETUP BELOW IS
+ NOT WHAT YOU WANT FOR YOUR PRODUCTION SYSTEM!
+
+ See http://wiki.apache.org/solr/SpellCheckComponent for details
+ on the request parameters.
+ -->
+ <requestHandler name="/spell" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <!-- Solr will use suggestions from both the 'default' spellchecker
+ and from the 'wordbreak' spellchecker and combine them.
+ collations (re-written queries) can include a combination of
+ corrections from both spellcheckers -->
+ <str name="spellcheck.dictionary">default</str>
+ <str name="spellcheck">on</str>
+ <str name="spellcheck.extendedResults">true</str>
+ <str name="spellcheck.count">10</str>
+ <str name="spellcheck.alternativeTermCount">5</str>
+ <str name="spellcheck.maxResultsForSuggest">5</str>
+ <str name="spellcheck.collate">true</str>
+ <str name="spellcheck.collateExtendedResults">true</str>
+ <str name="spellcheck.maxCollationTries">10</str>
+ <str name="spellcheck.maxCollations">5</str>
+ </lst>
+ <arr name="last-components">
+ <str>spellcheck</str>
+ </arr>
+ </requestHandler>
+
+ <!-- Term Vector Component
+
+ http://wiki.apache.org/solr/TermVectorComponent
+ -->
+ <searchComponent name="tvComponent" class="solr.TermVectorComponent"/>
+
+ <!-- A request handler for demonstrating the term vector component
+
+ This is purely as an example.
+
+ In reality you will likely want to add the component to your
+ already specified request handlers.
+ -->
+ <requestHandler name="/tvrh" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <bool name="tv">true</bool>
+ </lst>
+ <arr name="last-components">
+ <str>tvComponent</str>
+ </arr>
+ </requestHandler>
+
+ <!-- Clustering Component. (Omitted here. See the default Solr example for a typical configuration.) -->
+
+ <!-- Terms Component
+
+ http://wiki.apache.org/solr/TermsComponent
+
+ A component to return terms and document frequency of those
+ terms
+ -->
+ <searchComponent name="terms" class="solr.TermsComponent"/>
+
+ <!-- A request handler for demonstrating the terms component -->
+ <requestHandler name="/terms" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <bool name="terms">true</bool>
+ <bool name="distrib">false</bool>
+ </lst>
+ <arr name="components">
+ <str>terms</str>
+ </arr>
+ </requestHandler>
+
+
+ <!-- Query Elevation Component
+
+ http://wiki.apache.org/solr/QueryElevationComponent
+
+ a search component that enables you to configure the top
+ results for a given query regardless of the normal lucene
+ scoring.
+ -->
+ <searchComponent name="elevator" class="solr.QueryElevationComponent" >
+ <!-- pick a fieldType to analyze queries -->
+ <str name="queryFieldType">string</str>
+ </searchComponent>
+
+ <!-- A request handler for demonstrating the elevator component -->
+ <requestHandler name="/elevate" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ </lst>
+ <arr name="last-components">
+ <str>elevator</str>
+ </arr>
+ </requestHandler>
+
+ <!-- Highlighting Component
+
+ http://wiki.apache.org/solr/HighlightingParameters
+ -->
+ <searchComponent class="solr.HighlightComponent" name="highlight">
+ <highlighting>
+ <!-- Configure the standard fragmenter -->
+ <!-- This could most likely be commented out in the "default" case -->
+ <fragmenter name="gap"
+ default="true"
+ class="solr.highlight.GapFragmenter">
+ <lst name="defaults">
+ <int name="hl.fragsize">100</int>
+ </lst>
+ </fragmenter>
+
+ <!-- A regular-expression-based fragmenter
+ (for sentence extraction)
+ -->
+ <fragmenter name="regex"
+ class="solr.highlight.RegexFragmenter">
+ <lst name="defaults">
+ <!-- slightly smaller fragsizes work better because of slop -->
+ <int name="hl.fragsize">70</int>
+ <!-- allow 50% slop on fragment sizes -->
+ <float name="hl.regex.slop">0.5</float>
+ <!-- a basic sentence pattern -->
+ <str name="hl.regex.pattern">[-\w ,/\n\&quot;&apos;]{20,200}</str>
+ </lst>
+ </fragmenter>
+
+ <!-- Configure the standard formatter -->
+ <formatter name="html"
+ default="true"
+ class="solr.highlight.HtmlFormatter">
+ <lst name="defaults">
+ <str name="hl.simple.pre"><![CDATA[<em>]]></str>
+ <str name="hl.simple.post"><![CDATA[</em>]]></str>
+ </lst>
+ </formatter>
+
+ <!-- Configure the standard encoder -->
+ <encoder name="html"
+ class="solr.highlight.HtmlEncoder" />
+
+ <!-- Configure the standard fragListBuilder -->
+ <fragListBuilder name="simple"
+ class="solr.highlight.SimpleFragListBuilder"/>
+
+ <!-- Configure the single fragListBuilder -->
+ <fragListBuilder name="single"
+ class="solr.highlight.SingleFragListBuilder"/>
+
+ <!-- Configure the weighted fragListBuilder -->
+ <fragListBuilder name="weighted"
+ default="true"
+ class="solr.highlight.WeightedFragListBuilder"/>
+
+ <!-- default tag FragmentsBuilder -->
+ <fragmentsBuilder name="default"
+ default="true"
+ class="solr.highlight.ScoreOrderFragmentsBuilder">
+ <!--
+ <lst name="defaults">
+ <str name="hl.multiValuedSeparatorChar">/</str>
+ </lst>
+ -->
+ </fragmentsBuilder>
+
+ <!-- multi-colored tag FragmentsBuilder -->
+ <fragmentsBuilder name="colored"
+ class="solr.highlight.ScoreOrderFragmentsBuilder">
+ <lst name="defaults">
+ <str name="hl.tag.pre"><![CDATA[
+ <b style="background:yellow">,<b style="background:lawgreen">,
+ <b style="background:aquamarine">,<b style="background:magenta">,
+ <b style="background:palegreen">,<b style="background:coral">,
+ <b style="background:wheat">,<b style="background:khaki">,
+ <b style="background:lime">,<b style="background:deepskyblue">]]></str>
+ <str name="hl.tag.post"><![CDATA[</b>]]></str>
+ </lst>
+ </fragmentsBuilder>
+
+ <boundaryScanner name="default"
+ default="true"
+ class="solr.highlight.SimpleBoundaryScanner">
+ <lst name="defaults">
+ <str name="hl.bs.maxScan">10</str>
+ <str name="hl.bs.chars">.,!? &#9;&#10;&#13;</str>
+ </lst>
+ </boundaryScanner>
+
+ <boundaryScanner name="breakIterator"
+ class="solr.highlight.BreakIteratorBoundaryScanner">
+ <lst name="defaults">
+ <!-- type should be one of CHARACTER, WORD(default), LINE and SENTENCE -->
+ <str name="hl.bs.type">WORD</str>
+ <!-- language and country are used when constructing Locale object. -->
+ <!-- And the Locale object will be used when getting instance of BreakIterator -->
+ <str name="hl.bs.language">en</str>
+ <str name="hl.bs.country">US</str>
+ </lst>
+ </boundaryScanner>
+ </highlighting>
+ </searchComponent>
+
+ <!-- Update Processors
+
+ Chains of Update Processor Factories for dealing with Update
+ Requests can be declared, and then used by name in Update
+ Request Processors
+
+ http://wiki.apache.org/solr/UpdateRequestProcessor
+
+ -->
+
+ <!-- Add unknown fields to the schema
+
+ Field type guessing update processors that will
+ attempt to parse string-typed field values as Booleans, Longs,
+ Doubles, or Dates, and then add schema fields with the guessed
+ field types. Text content will be indexed as "text_general" as
+ well as a copy to a plain string version in *_str.
+
+ These require that the schema is both managed and mutable, by
+ declaring schemaFactory as ManagedIndexSchemaFactory, with
+ mutable specified as true.
+
+ See http://wiki.apache.org/solr/GuessingFieldTypes
+ -->
+ <updateProcessor class="solr.UUIDUpdateProcessorFactory" name="uuid"/>
+ <updateProcessor class="solr.RemoveBlankFieldUpdateProcessorFactory" name="remove-blank"/>
+ <updateProcessor class="solr.FieldNameMutatingUpdateProcessorFactory" name="field-name-mutating">
+ <str name="pattern">[^\w-\.]</str>
+ <str name="replacement">_</str>
+ </updateProcessor>
+ <updateProcessor class="solr.ParseBooleanFieldUpdateProcessorFactory" name="parse-boolean"/>
+ <updateProcessor class="solr.ParseLongFieldUpdateProcessorFactory" name="parse-long"/>
+ <updateProcessor class="solr.ParseDoubleFieldUpdateProcessorFactory" name="parse-double"/>
+ <updateProcessor class="solr.ParseDateFieldUpdateProcessorFactory" name="parse-date">
+ <arr name="format">
+ <str>yyyy-MM-dd['T'[HH:mm[:ss[.SSS]][z</str>
+ <str>yyyy-MM-dd['T'[HH:mm[:ss[,SSS]][z</str>
+ <str>yyyy-MM-dd HH:mm[:ss[.SSS]][z</str>
+ <str>yyyy-MM-dd HH:mm[:ss[,SSS]][z</str>
+ <str>[EEE, ]dd MMM yyyy HH:mm[:ss] z</str>
+ <str>EEEE, dd-MMM-yy HH:mm:ss z</str>
+ <str>EEE MMM ppd HH:mm:ss [z ]yyyy</str>
+ </arr>
+ </updateProcessor>
+
+ <!-- The update.autoCreateFields property can be turned to false to disable schemaless mode -->
+ <updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:false}"
+ processor="uuid,remove-blank,field-name-mutating,parse-boolean,parse-long,parse-double,parse-date">
+ <processor class="solr.LogUpdateProcessorFactory"/>
+ <processor class="solr.DistributedUpdateProcessorFactory"/>
+ <processor class="solr.RunUpdateProcessorFactory"/>
+ </updateRequestProcessorChain>
+
+ <!-- Deduplication
+
+ An example dedup update processor that creates the "id" field
+ on the fly based on the hash code of some other fields. This
+ example has overwriteDupes set to false since we are using the
+ id field as the signatureField and Solr will maintain
+ uniqueness based on that anyway.
+
+ -->
+ <!--
+ <updateRequestProcessorChain name="dedupe">
+ <processor class="solr.processor.SignatureUpdateProcessorFactory">
+ <bool name="enabled">true</bool>
+ <str name="signatureField">id</str>
+ <bool name="overwriteDupes">false</bool>
+ <str name="fields">name,features,cat</str>
+ <str name="signatureClass">solr.processor.Lookup3Signature</str>
+ </processor>
+ <processor class="solr.LogUpdateProcessorFactory" />
+ <processor class="solr.RunUpdateProcessorFactory" />
+ </updateRequestProcessorChain>
+ -->
+
+ <!-- Language identification
+
+ This example update chain identifies the language of the incoming
+ documents using the langid contrib. The detected language is
+ written to field language_s. No field name mapping is done.
+ The fields used for detection are text, title, subject and description,
+ making this example suitable for detecting languages form full-text
+ rich documents injected via ExtractingRequestHandler.
+ See more about langId at http://wiki.apache.org/solr/LanguageDetection
+ -->
+ <!--
+ <updateRequestProcessorChain name="langid">
+ <processor class="org.apache.solr.update.processor.TikaLanguageIdentifierUpdateProcessorFactory">
+ <str name="langid.fl">text,title,subject,description</str>
+ <str name="langid.langField">language_s</str>
+ <str name="langid.fallback">en</str>
+ </processor>
+ <processor class="solr.LogUpdateProcessorFactory" />
+ <processor class="solr.RunUpdateProcessorFactory" />
+ </updateRequestProcessorChain>
+ -->
+
+ <!-- Script update processor
+
+ This example hooks in an update processor implemented using JavaScript.
+
+ See more about the script update processor at http://wiki.apache.org/solr/ScriptUpdateProcessor
+ -->
+ <!--
+ <updateRequestProcessorChain name="script">
+ <processor class="solr.StatelessScriptUpdateProcessorFactory">
+ <str name="script">update-script.js</str>
+ <lst name="params">
+ <str name="config_param">example config parameter</str>
+ </lst>
+ </processor>
+ <processor class="solr.RunUpdateProcessorFactory" />
+ </updateRequestProcessorChain>
+ -->
+
+ <!-- Response Writers
+
+ http://wiki.apache.org/solr/QueryResponseWriter
+
+ Request responses will be written using the writer specified by
+ the 'wt' request parameter matching the name of a registered
+ writer.
+
+ The "default" writer is the default and will be used if 'wt' is
+ not specified in the request.
+ -->
+ <!-- The following response writers are implicitly configured unless
+ overridden...
+ -->
+ <!--
+ <queryResponseWriter name="xml"
+ default="true"
+ class="solr.XMLResponseWriter" />
+ <queryResponseWriter name="json" class="solr.JSONResponseWriter"/>
+ <queryResponseWriter name="python" class="solr.PythonResponseWriter"/>
+ <queryResponseWriter name="ruby" class="solr.RubyResponseWriter"/>
+ <queryResponseWriter name="php" class="solr.PHPResponseWriter"/>
+ <queryResponseWriter name="phps" class="solr.PHPSerializedResponseWriter"/>
+ <queryResponseWriter name="csv" class="solr.CSVResponseWriter"/>
+ <queryResponseWriter name="schema.xml" class="solr.SchemaXmlResponseWriter"/>
+ -->
+
+ <queryResponseWriter name="json" class="solr.JSONResponseWriter">
+ <!-- For the purposes of the tutorial, JSON responses are written as
+ plain text so that they are easy to read in *any* browser.
+ If you expect a MIME type of "application/json" just remove this override.
+ -->
+ <str name="content-type">text/plain; charset=UTF-8</str>
+ </queryResponseWriter>
+
+ <!--
+ Custom response writers can be declared as needed...
+ -->
+ <queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
+ <str name="template.base.dir">${velocity.template.base.dir:}</str>
+ <str name="solr.resource.loader.enabled">${velocity.solr.resource.loader.enabled:true}</str>
+ <str name="params.resource.loader.enabled">${velocity.params.resource.loader.enabled:false}</str>
+ </queryResponseWriter>
+
+ <!-- XSLT response writer transforms the XML output by any xslt file found
+ in Solr's conf/xslt directory. Changes to xslt files are checked for
+ every xsltCacheLifetimeSeconds.
+ -->
+ <queryResponseWriter name="xslt" class="solr.XSLTResponseWriter">
+ <int name="xsltCacheLifetimeSeconds">5</int>
+ </queryResponseWriter>
+
+ <!-- Query Parsers
+
+ https://lucene.apache.org/solr/guide/query-syntax-and-parsing.html
+
+ Multiple QParserPlugins can be registered by name, and then
+ used in either the "defType" param for the QueryComponent (used
+ by SearchHandler) or in LocalParams
+ -->
+ <!-- example of registering a query parser -->
+ <!--
+ <queryParser name="myparser" class="com.mycompany.MyQParserPlugin"/>
+ -->
+
+ <!-- Function Parsers
+
+ http://wiki.apache.org/solr/FunctionQuery
+
+ Multiple ValueSourceParsers can be registered by name, and then
+ used as function names when using the "func" QParser.
+ -->
+ <!-- example of registering a custom function parser -->
+ <!--
+ <valueSourceParser name="myfunc"
+ class="com.mycompany.MyValueSourceParser" />
+ -->
+
+
+ <!-- Document Transformers
+ http://wiki.apache.org/solr/DocTransformers
+ -->
+ <!--
+ Could be something like:
+ <transformer name="db" class="com.mycompany.LoadFromDatabaseTransformer" >
+ <int name="connection">jdbc://....</int>
+ </transformer>
+
+ To add a constant value to all docs, use:
+ <transformer name="mytrans2" class="org.apache.solr.response.transform.ValueAugmenterFactory" >
+ <int name="value">5</int>
+ </transformer>
+
+ If you want the user to still be able to change it with _value:something_ use this:
+ <transformer name="mytrans3" class="org.apache.solr.response.transform.ValueAugmenterFactory" >
+ <double name="defaultValue">5</double>
+ </transformer>
+
+ If you are using the QueryElevationComponent, you may wish to mark documents that get boosted. The
+ EditorialMarkerFactory will do exactly that:
+ <transformer name="qecBooster" class="org.apache.solr.response.transform.EditorialMarkerFactory" />
+ -->
+</config>
diff --git a/solr/conf/solrconfig.xml~ b/solr/conf/solrconfig.xml~
new file mode 100644
index 000000000..5854cf2d9
--- /dev/null
+++ b/solr/conf/solrconfig.xml~
@@ -0,0 +1,1358 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ For more details about configurations options that may appear in
+ this file, see http://wiki.apache.org/solr/SolrConfigXml.
+-->
+<config>
+ <!-- In all configuration below, a prefix of "solr." for class names
+ is an alias that causes solr to search appropriate packages,
+ including org.apache.solr.(search|update|request|core|analysis)
+
+ You may also specify a fully qualified Java classname if you
+ have your own custom plugins.
+ -->
+
+ <!-- Controls what version of Lucene various components of Solr
+ adhere to. Generally, you want to use the latest version to
+ get all bug fixes and improvements. It is highly recommended
+ that you fully re-index after changing this setting as it can
+ affect both how text is indexed and queried.
+ -->
+ <luceneMatchVersion>8.0.0</luceneMatchVersion>
+
+ <!-- <lib/> directives can be used to instruct Solr to load any Jars
+ identified and use them to resolve any "plugins" specified in
+ your solrconfig.xml or schema.xml (ie: Analyzers, Request
+ Handlers, etc...).
+
+ All directories and paths are resolved relative to the
+ instanceDir.
+
+ Please note that <lib/> directives are processed in the order
+ that they appear in your solrconfig.xml file, and are "stacked"
+ on top of each other when building a ClassLoader - so if you have
+ plugin jars with dependencies on other jars, the "lower level"
+ dependency jars should be loaded first.
+
+ If a "./lib" directory exists in your instanceDir, all files
+ found in it are included as if you had used the following
+ syntax...
+
+ <lib dir="./lib" />
+ -->
+
+ <!-- A 'dir' option by itself adds any files found in the directory
+ to the classpath, this is useful for including all jars in a
+ directory.
+
+ When a 'regex' is specified in addition to a 'dir', only the
+ files in that directory which completely match the regex
+ (anchored on both ends) will be included.
+
+ If a 'dir' option (with or without a regex) is used and nothing
+ is found that matches, a warning will be logged.
+
+ The examples below can be used to load some solr-contribs along
+ with their external dependencies.
+ -->
+ <lib dir="${solr.install.dir:../../../..}/contrib/extraction/lib" regex=".*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-cell-\d.*\.jar" />
+
+ <lib dir="${solr.install.dir:../../../..}/contrib/clustering/lib/" regex=".*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-clustering-\d.*\.jar" />
+
+ <lib dir="${solr.install.dir:../../../..}/contrib/langid/lib/" regex=".*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-langid-\d.*\.jar" />
+
+ <lib dir="${solr.install.dir:../../../..}/contrib/velocity/lib" regex=".*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-velocity-\d.*\.jar" />
+ <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-ltr-\d.*\.jar" />
+
+ <!-- an exact 'path' can be used instead of a 'dir' to specify a
+ specific jar file. This will cause a serious error to be logged
+ if it can't be loaded.
+ -->
+ <!--
+ <lib path="../a-jar-that-does-not-exist.jar" />
+ -->
+
+ <!-- Data Directory
+
+ Used to specify an alternate directory to hold all index data
+ other than the default ./data under the Solr home. If
+ replication is in use, this should match the replication
+ configuration.
+ -->
+ <dataDir>${solr.data.dir:}</dataDir>
+
+
+ <!-- The DirectoryFactory to use for indexes.
+
+ solr.StandardDirectoryFactory is filesystem
+ based and tries to pick the best implementation for the current
+ JVM and platform. solr.NRTCachingDirectoryFactory, the default,
+ wraps solr.StandardDirectoryFactory and caches small files in memory
+ for better NRT performance.
+
+ One can force a particular implementation via solr.MMapDirectoryFactory,
+ solr.NIOFSDirectoryFactory, or solr.SimpleFSDirectoryFactory.
+
+ solr.RAMDirectoryFactory is memory based and not persistent.
+ -->
+ <directoryFactory name="DirectoryFactory"
+ class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
+
+ <schemaFactory class="ClassicIndexSchemaFactory"/>
+ <!-- The CodecFactory for defining the format of the inverted index.
+ The default implementation is SchemaCodecFactory, which is the official Lucene
+ index format, but hooks into the schema to provide per-field customization of
+ the postings lists and per-document values in the fieldType element
+ (postingsFormat/docValuesFormat). Note that most of the alternative implementations
+ are experimental, so if you choose to customize the index format, it's a good
+ idea to convert back to the official format e.g. via IndexWriter.addIndexes(IndexReader)
+ before upgrading to a newer version to avoid unnecessary reindexing.
+ A "compressionMode" string element can be added to <codecFactory> to choose
+ between the existing compression modes in the default codec: "BEST_SPEED" (default)
+ or "BEST_COMPRESSION".
+ -->
+ <codecFactory class="solr.SchemaCodecFactory"/>
+
+ <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Index Config - These settings control low-level behavior of indexing
+ Most example settings here show the default value, but are commented
+ out, to more easily see where customizations have been made.
+
+ Note: This replaces <indexDefaults> and <mainIndex> from older versions
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
+ <indexConfig>
+ <!-- maxFieldLength was removed in 4.0. To get similar behavior, include a
+ LimitTokenCountFilterFactory in your fieldType definition. E.g.
+ <filter class="solr.LimitTokenCountFilterFactory" maxTokenCount="10000"/>
+ -->
+ <!-- Maximum time to wait for a write lock (ms) for an IndexWriter. Default: 1000 -->
+ <!-- <writeLockTimeout>1000</writeLockTimeout> -->
+
+ <!-- Expert: Enabling compound file will use less files for the index,
+ using fewer file descriptors on the expense of performance decrease.
+ Default in Lucene is "true". Default in Solr is "false" (since 3.6) -->
+ <!-- <useCompoundFile>false</useCompoundFile> -->
+
+ <!-- ramBufferSizeMB sets the amount of RAM that may be used by Lucene
+ indexing for buffering added documents and deletions before they are
+ flushed to the Directory.
+ maxBufferedDocs sets a limit on the number of documents buffered
+ before flushing.
+ If both ramBufferSizeMB and maxBufferedDocs is set, then
+ Lucene will flush based on whichever limit is hit first. -->
+ <!-- <ramBufferSizeMB>100</ramBufferSizeMB> -->
+ <!-- <maxBufferedDocs>1000</maxBufferedDocs> -->
+
+ <!-- Expert: Merge Policy
+ The Merge Policy in Lucene controls how merging of segments is done.
+ The default since Solr/Lucene 3.3 is TieredMergePolicy.
+ The default since Lucene 2.3 was the LogByteSizeMergePolicy,
+ Even older versions of Lucene used LogDocMergePolicy.
+ -->
+ <!--
+ <mergePolicyFactory class="org.apache.solr.index.TieredMergePolicyFactory">
+ <int name="maxMergeAtOnce">10</int>
+ <int name="segmentsPerTier">10</int>
+ <double name="noCFSRatio">0.1</double>
+ </mergePolicyFactory>
+ -->
+
+ <!-- Expert: Merge Scheduler
+ The Merge Scheduler in Lucene controls how merges are
+ performed. The ConcurrentMergeScheduler (Lucene 2.3 default)
+ can perform merges in the background using separate threads.
+ The SerialMergeScheduler (Lucene 2.2 default) does not.
+ -->
+ <!--
+ <mergeScheduler class="org.apache.lucene.index.ConcurrentMergeScheduler"/>
+ -->
+
+ <!-- LockFactory
+
+ This option specifies which Lucene LockFactory implementation
+ to use.
+
+ single = SingleInstanceLockFactory - suggested for a
+ read-only index or when there is no possibility of
+ another process trying to modify the index.
+ native = NativeFSLockFactory - uses OS native file locking.
+ Do not use when multiple solr webapps in the same
+ JVM are attempting to share a single index.
+ simple = SimpleFSLockFactory - uses a plain file for locking
+
+ Defaults: 'native' is default for Solr3.6 and later, otherwise
+ 'simple' is the default
+
+ More details on the nuances of each LockFactory...
+ http://wiki.apache.org/lucene-java/AvailableLockFactories
+ -->
+ <lockType>${solr.lock.type:native}</lockType>
+
+ <!-- Commit Deletion Policy
+ Custom deletion policies can be specified here. The class must
+ implement org.apache.lucene.index.IndexDeletionPolicy.
+
+ The default Solr IndexDeletionPolicy implementation supports
+ deleting index commit points on number of commits, age of
+ commit point and optimized status.
+
+ The latest commit point should always be preserved regardless
+ of the criteria.
+ -->
+ <!--
+ <deletionPolicy class="solr.SolrDeletionPolicy">
+ -->
+ <!-- The number of commit points to be kept -->
+ <!-- <str name="maxCommitsToKeep">1</str> -->
+ <!-- The number of optimized commit points to be kept -->
+ <!-- <str name="maxOptimizedCommitsToKeep">0</str> -->
+ <!--
+ Delete all commit points once they have reached the given age.
+ Supports DateMathParser syntax e.g.
+ -->
+ <!--
+ <str name="maxCommitAge">30MINUTES</str>
+ <str name="maxCommitAge">1DAY</str>
+ -->
+ <!--
+ </deletionPolicy>
+ -->
+
+ <!-- Lucene Infostream
+
+ To aid in advanced debugging, Lucene provides an "InfoStream"
+ of detailed information when indexing.
+
+ Setting The value to true will instruct the underlying Lucene
+ IndexWriter to write its debugging info the specified file
+ -->
+ <!-- <infoStream file="INFOSTREAM.txt">false</infoStream> -->
+ </indexConfig>
+
+
+ <!-- JMX
+
+ This example enables JMX if and only if an existing MBeanServer
+ is found, use this if you want to configure JMX through JVM
+ parameters. Remove this to disable exposing Solr configuration
+ and statistics to JMX.
+
+ For more details see http://wiki.apache.org/solr/SolrJmx
+ -->
+ <jmx />
+ <!-- If you want to connect to a particular server, specify the
+ agentId
+ -->
+ <!-- <jmx agentId="myAgent" /> -->
+ <!-- If you want to start a new MBeanServer, specify the serviceUrl -->
+ <!-- <jmx serviceUrl="service:jmx:rmi:///jndi/rmi://localhost:9999/solr"/>
+ -->
+
+ <!-- The default high-performance update handler -->
+ <updateHandler class="solr.DirectUpdateHandler2">
+
+ <!-- Enables a transaction log, used for real-time get, durability, and
+ and solr cloud replica recovery. The log can grow as big as
+ uncommitted changes to the index, so use of a hard autoCommit
+ is recommended (see below).
+ "dir" - the target directory for transaction logs, defaults to the
+ solr data directory.
+ "numVersionBuckets" - sets the number of buckets used to keep
+ track of max version values when checking for re-ordered
+ updates; increase this value to reduce the cost of
+ synchronizing access to version buckets during high-volume
+ indexing, this requires 8 bytes (long) * numVersionBuckets
+ of heap space per Solr core.
+ -->
+ <updateLog>
+ <str name="dir">${solr.ulog.dir:}</str>
+ <int name="numVersionBuckets">${solr.ulog.numVersionBuckets:65536}</int>
+ </updateLog>
+
+ <!-- AutoCommit
+
+ Perform a hard commit automatically under certain conditions.
+ Instead of enabling autoCommit, consider using "commitWithin"
+ when adding documents.
+
+ http://wiki.apache.org/solr/UpdateXmlMessages
+
+ maxDocs - Maximum number of documents to add since the last
+ commit before automatically triggering a new commit.
+
+ maxTime - Maximum amount of time in ms that is allowed to pass
+ since a document was added before automatically
+ triggering a new commit.
+ openSearcher - if false, the commit causes recent index changes
+ to be flushed to stable storage, but does not cause a new
+ searcher to be opened to make those changes visible.
+
+ If the updateLog is enabled, then it's highly recommended to
+ have some sort of hard autoCommit to limit the log size.
+ -->
+ <autoCommit>
+ <maxTime>600000</maxTime>
+ <openSearcher>false</openSearcher>
+ </autoCommit>
+
+ <!-- softAutoCommit is like autoCommit except it causes a
+ 'soft' commit which only ensures that changes are visible
+ but does not ensure that data is synced to disk. This is
+ faster and more near-realtime friendly than a hard commit.
+ -->
+
+ <autoSoftCommit>
+ <maxTime>1000</maxTime>
+ </autoSoftCommit>
+
+ <!-- Update Related Event Listeners
+
+ Various IndexWriter related events can trigger Listeners to
+ take actions.
+
+ postCommit - fired after every commit or optimize command
+ postOptimize - fired after every optimize command
+ -->
+
+ </updateHandler>
+
+ <!-- IndexReaderFactory
+
+ Use the following format to specify a custom IndexReaderFactory,
+ which allows for alternate IndexReader implementations.
+
+ ** Experimental Feature **
+
+ Please note - Using a custom IndexReaderFactory may prevent
+ certain other features from working. The API to
+ IndexReaderFactory may change without warning or may even be
+ removed from future releases if the problems cannot be
+ resolved.
+
+
+ ** Features that may not work with custom IndexReaderFactory **
+
+ The ReplicationHandler assumes a disk-resident index. Using a
+ custom IndexReader implementation may cause incompatibility
+ with ReplicationHandler and may cause replication to not work
+ correctly. See SOLR-1366 for details.
+
+ -->
+ <!--
+ <indexReaderFactory name="IndexReaderFactory" class="package.class">
+ <str name="someArg">Some Value</str>
+ </indexReaderFactory >
+ -->
+
+ <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Query section - these settings control query time things like caches
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
+ <query>
+
+ <!-- Maximum number of clauses in each BooleanQuery, an exception
+ is thrown if exceeded. It is safe to increase or remove this setting,
+ since it is purely an arbitrary limit to try and catch user errors where
+ large boolean queries may not be the best implementation choice.
+ -->
+ <maxBooleanClauses>${solr.max.booleanClauses:1024}</maxBooleanClauses>
+
+ <!-- Solr Internal Query Caches
+
+ There are two implementations of cache available for Solr,
+ LRUCache, based on a synchronized LinkedHashMap, and
+ FastLRUCache, based on a ConcurrentHashMap.
+
+ FastLRUCache has faster gets and slower puts in single
+ threaded operation and thus is generally faster than LRUCache
+ when the hit ratio of the cache is high (> 75%), and may be
+ faster under other scenarios on multi-cpu systems.
+ -->
+
+ <!-- Filter Cache
+
+ Cache used by SolrIndexSearcher for filters (DocSets),
+ unordered sets of *all* documents that match a query. When a
+ new searcher is opened, its caches may be prepopulated or
+ "autowarmed" using data from caches in the old searcher.
+ autowarmCount is the number of items to prepopulate. For
+ LRUCache, the autowarmed items will be the most recently
+ accessed items.
+
+ Parameters:
+ class - the SolrCache implementation LRUCache or
+ (LRUCache or FastLRUCache)
+ size - the maximum number of entries in the cache
+ initialSize - the initial capacity (number of entries) of
+ the cache. (see java.util.HashMap)
+ autowarmCount - the number of entries to prepopulate from
+ and old cache.
+ maxRamMB - the maximum amount of RAM (in MB) that this cache is allowed
+ to occupy. Note that when this option is specified, the size
+ and initialSize parameters are ignored.
+ -->
+ <filterCache class="solr.FastLRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="0"/>
+
+ <!-- Query Result Cache
+
+ Caches results of searches - ordered lists of document ids
+ (DocList) based on a query, a sort, and the range of documents requested.
+ Additional supported parameter by LRUCache:
+ maxRamMB - the maximum amount of RAM (in MB) that this cache is allowed
+ to occupy
+ -->
+ <queryResultCache class="solr.LRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="0"/>
+
+ <!-- Document Cache
+
+ Caches Lucene Document objects (the stored fields for each
+ document). Since Lucene internal document ids are transient,
+ this cache will not be autowarmed.
+ -->
+ <documentCache class="solr.LRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="0"/>
+
+ <!-- custom cache currently used by block join -->
+ <cache name="perSegFilter"
+ class="solr.search.LRUCache"
+ size="10"
+ initialSize="0"
+ autowarmCount="10"
+ regenerator="solr.NoOpRegenerator" />
+
+ <!-- Field Value Cache
+
+ Cache used to hold field values that are quickly accessible
+ by document id. The fieldValueCache is created by default
+ even if not configured here.
+ -->
+ <!--
+ <fieldValueCache class="solr.FastLRUCache"
+ size="512"
+ autowarmCount="128"
+ showItems="32" />
+ -->
+
+ <!-- Custom Cache
+
+ Example of a generic cache. These caches may be accessed by
+ name through SolrIndexSearcher.getCache(),cacheLookup(), and
+ cacheInsert(). The purpose is to enable easy caching of
+ user/application level data. The regenerator argument should
+ be specified as an implementation of solr.CacheRegenerator
+ if autowarming is desired.
+ -->
+ <!--
+ <cache name="myUserCache"
+ class="solr.LRUCache"
+ size="4096"
+ initialSize="1024"
+ autowarmCount="1024"
+ regenerator="com.mycompany.MyRegenerator"
+ />
+ -->
+
+
+ <!-- Lazy Field Loading
+
+ If true, stored fields that are not requested will be loaded
+ lazily. This can result in a significant speed improvement
+ if the usual case is to not load all stored fields,
+ especially if the skipped fields are large compressed text
+ fields.
+ -->
+ <enableLazyFieldLoading>true</enableLazyFieldLoading>
+
+ <!-- Use Filter For Sorted Query
+
+ A possible optimization that attempts to use a filter to
+ satisfy a search. If the requested sort does not include
+ score, then the filterCache will be checked for a filter
+ matching the query. If found, the filter will be used as the
+ source of document ids, and then the sort will be applied to
+ that.
+
+ For most situations, this will not be useful unless you
+ frequently get the same search repeatedly with different sort
+ options, and none of them ever use "score"
+ -->
+ <!--
+ <useFilterForSortedQuery>true</useFilterForSortedQuery>
+ -->
+
+ <!-- Result Window Size
+
+ An optimization for use with the queryResultCache. When a search
+ is requested, a superset of the requested number of document ids
+ are collected. For example, if a search for a particular query
+ requests matching documents 10 through 19, and queryWindowSize is 50,
+ then documents 0 through 49 will be collected and cached. Any further
+ requests in that range can be satisfied via the cache.
+ -->
+ <queryResultWindowSize>20</queryResultWindowSize>
+
+ <!-- Maximum number of documents to cache for any entry in the
+ queryResultCache.
+ -->
+ <queryResultMaxDocsCached>200</queryResultMaxDocsCached>
+
+ <!-- Query Related Event Listeners
+
+ Various IndexSearcher related events can trigger Listeners to
+ take actions.
+
+ newSearcher - fired whenever a new searcher is being prepared
+ and there is a current searcher handling requests (aka
+ registered). It can be used to prime certain caches to
+ prevent long request times for certain requests.
+
+ firstSearcher - fired whenever a new searcher is being
+ prepared but there is no current registered searcher to handle
+ requests or to gain autowarming data from.
+
+
+ -->
+ <!-- QuerySenderListener takes an array of NamedList and executes a
+ local query request for each NamedList in sequence.
+ -->
+ <listener event="newSearcher" class="solr.QuerySenderListener">
+ <arr name="queries">
+ <!--
+ <lst><str name="q">solr</str><str name="sort">price asc</str></lst>
+ <lst><str name="q">rocks</str><str name="sort">weight asc</str></lst>
+ -->
+ </arr>
+ </listener>
+ <listener event="firstSearcher" class="solr.QuerySenderListener">
+ <arr name="queries">
+ <!--
+ <lst>
+ <str name="q">static firstSearcher warming in solrconfig.xml</str>
+ </lst>
+ -->
+ </arr>
+ </listener>
+
+ <!-- Use Cold Searcher
+
+ If a search request comes in and there is no current
+ registered searcher, then immediately register the still
+ warming searcher and use it. If "false" then all requests
+ will block until the first searcher is done warming.
+ -->
+ <useColdSearcher>false</useColdSearcher>
+
+ </query>
+
+
+ <!-- Request Dispatcher
+
+ This section contains instructions for how the SolrDispatchFilter
+ should behave when processing requests for this SolrCore.
+
+ -->
+ <requestDispatcher>
+ <!-- Request Parsing
+
+ These settings indicate how Solr Requests may be parsed, and
+ what restrictions may be placed on the ContentStreams from
+ those requests
+
+ enableRemoteStreaming - enables use of the stream.file
+ and stream.url parameters for specifying remote streams.
+
+ multipartUploadLimitInKB - specifies the max size (in KiB) of
+ Multipart File Uploads that Solr will allow in a Request.
+
+ formdataUploadLimitInKB - specifies the max size (in KiB) of
+ form data (application/x-www-form-urlencoded) sent via
+ POST. You can use POST to pass request parameters not
+ fitting into the URL.
+
+ addHttpRequestToContext - if set to true, it will instruct
+ the requestParsers to include the original HttpServletRequest
+ object in the context map of the SolrQueryRequest under the
+ key "httpRequest". It will not be used by any of the existing
+ Solr components, but may be useful when developing custom
+ plugins.
+
+ *** WARNING ***
+ Before enabling remote streaming, you should make sure your
+ system has authentication enabled.
+
+ <requestParsers enableRemoteStreaming="false"
+ multipartUploadLimitInKB="-1"
+ formdataUploadLimitInKB="-1"
+ addHttpRequestToContext="false"/>
+ -->
+
+ <!-- HTTP Caching
+
+ Set HTTP caching related parameters (for proxy caches and clients).
+
+ The options below instruct Solr not to output any HTTP Caching
+ related headers
+ -->
+ <httpCaching never304="true" />
+ <!-- If you include a <cacheControl> directive, it will be used to
+ generate a Cache-Control header (as well as an Expires header
+ if the value contains "max-age=")
+
+ By default, no Cache-Control header is generated.
+
+ You can use the <cacheControl> option even if you have set
+ never304="true"
+ -->
+ <!--
+ <httpCaching never304="true" >
+ <cacheControl>max-age=30, public</cacheControl>
+ </httpCaching>
+ -->
+ <!-- To enable Solr to respond with automatically generated HTTP
+ Caching headers, and to response to Cache Validation requests
+ correctly, set the value of never304="false"
+
+ This will cause Solr to generate Last-Modified and ETag
+ headers based on the properties of the Index.
+
+ The following options can also be specified to affect the
+ values of these headers...
+
+ lastModFrom - the default value is "openTime" which means the
+ Last-Modified value (and validation against If-Modified-Since
+ requests) will all be relative to when the current Searcher
+ was opened. You can change it to lastModFrom="dirLastMod" if
+ you want the value to exactly correspond to when the physical
+ index was last modified.
+
+ etagSeed="..." is an option you can change to force the ETag
+ header (and validation against If-None-Match requests) to be
+ different even if the index has not changed (ie: when making
+ significant changes to your config file)
+
+ (lastModifiedFrom and etagSeed are both ignored if you use
+ the never304="true" option)
+ -->
+ <!--
+ <httpCaching lastModifiedFrom="openTime"
+ etagSeed="Solr">
+ <cacheControl>max-age=30, public</cacheControl>
+ </httpCaching>
+ -->
+ </requestDispatcher>
+
+ <!-- Request Handlers
+
+ http://wiki.apache.org/solr/SolrRequestHandler
+
+ Incoming queries will be dispatched to a specific handler by name
+ based on the path specified in the request.
+
+ If a Request Handler is declared with startup="lazy", then it will
+ not be initialized until the first request that uses it.
+
+ -->
+ <!-- SearchHandler
+
+ http://wiki.apache.org/solr/SearchHandler
+
+ For processing Search Queries, the primary Request Handler
+ provided with Solr is "SearchHandler" It delegates to a sequent
+ of SearchComponents (see below) and supports distributed
+ queries across multiple shards
+ -->
+ <requestHandler name="/select" class="solr.SearchHandler">
+ <!-- default values for query parameters can be specified, these
+ will be overridden by parameters in the request
+ -->
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ <int name="rows">10</int>
+ <str name="df">text</str>
+ <!-- Default search field
+ <str name="df">text</str>
+ -->
+ <!-- Change from JSON to XML format (the default prior to Solr 7.0)
+ <str name="wt">xml</str>
+ -->
+ </lst>
+ <!-- In addition to defaults, "appends" params can be specified
+ to identify values which should be appended to the list of
+ multi-val params from the query (or the existing "defaults").
+ -->
+ <!-- In this example, the param "fq=instock:true" would be appended to
+ any query time fq params the user may specify, as a mechanism for
+ partitioning the index, independent of any user selected filtering
+ that may also be desired (perhaps as a result of faceted searching).
+
+ NOTE: there is *absolutely* nothing a client can do to prevent these
+ "appends" values from being used, so don't use this mechanism
+ unless you are sure you always want it.
+ -->
+ <!--
+ <lst name="appends">
+ <str name="fq">inStock:true</str>
+ </lst>
+ -->
+ <!-- "invariants" are a way of letting the Solr maintainer lock down
+ the options available to Solr clients. Any params values
+ specified here are used regardless of what values may be specified
+ in either the query, the "defaults", or the "appends" params.
+
+ In this example, the facet.field and facet.query params would
+ be fixed, limiting the facets clients can use. Faceting is
+ not turned on by default - but if the client does specify
+ facet=true in the request, these are the only facets they
+ will be able to see counts for; regardless of what other
+ facet.field or facet.query params they may specify.
+
+ NOTE: there is *absolutely* nothing a client can do to prevent these
+ "invariants" values from being used, so don't use this mechanism
+ unless you are sure you always want it.
+ -->
+ <!--
+ <lst name="invariants">
+ <str name="facet.field">cat</str>
+ <str name="facet.field">manu_exact</str>
+ <str name="facet.query">price:[* TO 500]</str>
+ <str name="facet.query">price:[500 TO *]</str>
+ </lst>
+ -->
+ <!-- If the default list of SearchComponents is not desired, that
+ list can either be overridden completely, or components can be
+ prepended or appended to the default list. (see below)
+ -->
+ <!--
+ <arr name="components">
+ <str>nameOfCustomComponent1</str>
+ <str>nameOfCustomComponent2</str>
+ </arr>
+ -->
+ </requestHandler>
+
+ <!-- A request handler that returns indented JSON by default -->
+ <requestHandler name="/query" class="solr.SearchHandler">
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ <str name="wt">json</str>
+ <str name="indent">true</str>
+ </lst>
+ </requestHandler>
+
+
+ <!-- A Robust Example
+
+ This example SearchHandler declaration shows off usage of the
+ SearchHandler with many defaults declared
+
+ Note that multiple instances of the same Request Handler
+ (SearchHandler) can be registered multiple times with different
+ names (and different init parameters)
+ -->
+ <requestHandler name="/browse" class="solr.SearchHandler" useParams="query,facets,velocity,browse">
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ </lst>
+ </requestHandler>
+
+ <initParams path="/update/**,/query,/select,/tvrh,/elevate,/spell,/browse">
+ <lst name="defaults">
+ <str name="df">_text_</str>
+ </lst>
+ </initParams>
+
+ <!-- Solr Cell Update Request Handler
+
+ http://wiki.apache.org/solr/ExtractingRequestHandler
+
+ -->
+ <requestHandler name="/update/extract"
+ startup="lazy"
+ class="solr.extraction.ExtractingRequestHandler" >
+ <lst name="defaults">
+ <str name="lowernames">true</str>
+ <str name="fmap.content">_text_</str>
+ </lst>
+ </requestHandler>
+
+ <!-- Search Components
+
+ Search components are registered to SolrCore and used by
+ instances of SearchHandler (which can access them by name)
+
+ By default, the following components are available:
+
+ <searchComponent name="query" class="solr.QueryComponent" />
+ <searchComponent name="facet" class="solr.FacetComponent" />
+ <searchComponent name="mlt" class="solr.MoreLikeThisComponent" />
+ <searchComponent name="highlight" class="solr.HighlightComponent" />
+ <searchComponent name="stats" class="solr.StatsComponent" />
+ <searchComponent name="debug" class="solr.DebugComponent" />
+
+ Default configuration in a requestHandler would look like:
+
+ <arr name="components">
+ <str>query</str>
+ <str>facet</str>
+ <str>mlt</str>
+ <str>highlight</str>
+ <str>stats</str>
+ <str>debug</str>
+ </arr>
+
+ If you register a searchComponent to one of the standard names,
+ that will be used instead of the default.
+
+ To insert components before or after the 'standard' components, use:
+
+ <arr name="first-components">
+ <str>myFirstComponentName</str>
+ </arr>
+
+ <arr name="last-components">
+ <str>myLastComponentName</str>
+ </arr>
+
+ NOTE: The component registered with the name "debug" will
+ always be executed after the "last-components"
+
+ -->
+
+ <!-- Spell Check
+
+ The spell check component can return a list of alternative spelling
+ suggestions.
+
+ http://wiki.apache.org/solr/SpellCheckComponent
+ -->
+ <!-- <searchComponent name="spellcheck" class="solr.SpellCheckComponent">
+
+ <str name="queryAnalyzerFieldType">text_general</str>
+
+ <!-- Multiple "Spell Checkers" can be declared and used by this
+ component
+ -->
+
+ <!-- a spellchecker built from a field of the main index -->
+ <lst name="spellchecker">
+ <str name="name">default</str>
+ <str name="field">_text_</str>
+ <str name="classname">solr.DirectSolrSpellChecker</str>
+ <!-- the spellcheck distance measure used, the default is the internal levenshtein -->
+ <str name="distanceMeasure">internal</str>
+ <!-- minimum accuracy needed to be considered a valid spellcheck suggestion -->
+ <float name="accuracy">0.5</float>
+ <!-- the maximum #edits we consider when enumerating terms: can be 1 or 2 -->
+ <int name="maxEdits">2</int>
+ <!-- the minimum shared prefix when enumerating terms -->
+ <int name="minPrefix">1</int>
+ <!-- maximum number of inspections per result. -->
+ <int name="maxInspections">5</int>
+ <!-- minimum length of a query term to be considered for correction -->
+ <int name="minQueryLength">4</int>
+ <!-- maximum threshold of documents a query term can appear to be considered for correction -->
+ <float name="maxQueryFrequency">0.01</float>
+ <!-- uncomment this to require suggestions to occur in 1% of the documents
+ <float name="thresholdTokenFrequency">.01</float>
+ -->
+ </lst>
+
+ <!-- a spellchecker that can break or combine words. See "/spell" handler below for usage -->
+ <!--
+ <lst name="spellchecker">
+ <str name="name">wordbreak</str>
+ <str name="classname">solr.WordBreakSolrSpellChecker</str>
+ <str name="field">name</str>
+ <str name="combineWords">true</str>
+ <str name="breakWords">true</str>
+ <int name="maxChanges">10</int>
+ </lst>
+ -->
+ </searchComponent>
+ -->
+
+ <!-- A request handler for demonstrating the spellcheck component.
+
+ NOTE: This is purely as an example. The whole purpose of the
+ SpellCheckComponent is to hook it into the request handler that
+ handles your normal user queries so that a separate request is
+ not needed to get suggestions.
+
+ IN OTHER WORDS, THERE IS REALLY GOOD CHANCE THE SETUP BELOW IS
+ NOT WHAT YOU WANT FOR YOUR PRODUCTION SYSTEM!
+
+ See http://wiki.apache.org/solr/SpellCheckComponent for details
+ on the request parameters.
+ -->
+ <requestHandler name="/spell" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <!-- Solr will use suggestions from both the 'default' spellchecker
+ and from the 'wordbreak' spellchecker and combine them.
+ collations (re-written queries) can include a combination of
+ corrections from both spellcheckers -->
+ <str name="spellcheck.dictionary">default</str>
+ <str name="spellcheck">on</str>
+ <str name="spellcheck.extendedResults">true</str>
+ <str name="spellcheck.count">10</str>
+ <str name="spellcheck.alternativeTermCount">5</str>
+ <str name="spellcheck.maxResultsForSuggest">5</str>
+ <str name="spellcheck.collate">true</str>
+ <str name="spellcheck.collateExtendedResults">true</str>
+ <str name="spellcheck.maxCollationTries">10</str>
+ <str name="spellcheck.maxCollations">5</str>
+ </lst>
+ <arr name="last-components">
+ <str>spellcheck</str>
+ </arr>
+ </requestHandler>
+
+ <!-- Term Vector Component
+
+ http://wiki.apache.org/solr/TermVectorComponent
+ -->
+ <searchComponent name="tvComponent" class="solr.TermVectorComponent"/>
+
+ <!-- A request handler for demonstrating the term vector component
+
+ This is purely as an example.
+
+ In reality you will likely want to add the component to your
+ already specified request handlers.
+ -->
+ <requestHandler name="/tvrh" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <bool name="tv">true</bool>
+ </lst>
+ <arr name="last-components">
+ <str>tvComponent</str>
+ </arr>
+ </requestHandler>
+
+ <!-- Clustering Component. (Omitted here. See the default Solr example for a typical configuration.) -->
+
+ <!-- Terms Component
+
+ http://wiki.apache.org/solr/TermsComponent
+
+ A component to return terms and document frequency of those
+ terms
+ -->
+ <searchComponent name="terms" class="solr.TermsComponent"/>
+
+ <!-- A request handler for demonstrating the terms component -->
+ <requestHandler name="/terms" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <bool name="terms">true</bool>
+ <bool name="distrib">false</bool>
+ </lst>
+ <arr name="components">
+ <str>terms</str>
+ </arr>
+ </requestHandler>
+
+
+ <!-- Query Elevation Component
+
+ http://wiki.apache.org/solr/QueryElevationComponent
+
+ a search component that enables you to configure the top
+ results for a given query regardless of the normal lucene
+ scoring.
+ -->
+ <searchComponent name="elevator" class="solr.QueryElevationComponent" >
+ <!-- pick a fieldType to analyze queries -->
+ <str name="queryFieldType">string</str>
+ </searchComponent>
+
+ <!-- A request handler for demonstrating the elevator component -->
+ <requestHandler name="/elevate" class="solr.SearchHandler" startup="lazy">
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ </lst>
+ <arr name="last-components">
+ <str>elevator</str>
+ </arr>
+ </requestHandler>
+
+ <!-- Highlighting Component
+
+ http://wiki.apache.org/solr/HighlightingParameters
+ -->
+ <searchComponent class="solr.HighlightComponent" name="highlight">
+ <highlighting>
+ <!-- Configure the standard fragmenter -->
+ <!-- This could most likely be commented out in the "default" case -->
+ <fragmenter name="gap"
+ default="true"
+ class="solr.highlight.GapFragmenter">
+ <lst name="defaults">
+ <int name="hl.fragsize">100</int>
+ </lst>
+ </fragmenter>
+
+ <!-- A regular-expression-based fragmenter
+ (for sentence extraction)
+ -->
+ <fragmenter name="regex"
+ class="solr.highlight.RegexFragmenter">
+ <lst name="defaults">
+ <!-- slightly smaller fragsizes work better because of slop -->
+ <int name="hl.fragsize">70</int>
+ <!-- allow 50% slop on fragment sizes -->
+ <float name="hl.regex.slop">0.5</float>
+ <!-- a basic sentence pattern -->
+ <str name="hl.regex.pattern">[-\w ,/\n\&quot;&apos;]{20,200}</str>
+ </lst>
+ </fragmenter>
+
+ <!-- Configure the standard formatter -->
+ <formatter name="html"
+ default="true"
+ class="solr.highlight.HtmlFormatter">
+ <lst name="defaults">
+ <str name="hl.simple.pre"><![CDATA[<em>]]></str>
+ <str name="hl.simple.post"><![CDATA[</em>]]></str>
+ </lst>
+ </formatter>
+
+ <!-- Configure the standard encoder -->
+ <encoder name="html"
+ class="solr.highlight.HtmlEncoder" />
+
+ <!-- Configure the standard fragListBuilder -->
+ <fragListBuilder name="simple"
+ class="solr.highlight.SimpleFragListBuilder"/>
+
+ <!-- Configure the single fragListBuilder -->
+ <fragListBuilder name="single"
+ class="solr.highlight.SingleFragListBuilder"/>
+
+ <!-- Configure the weighted fragListBuilder -->
+ <fragListBuilder name="weighted"
+ default="true"
+ class="solr.highlight.WeightedFragListBuilder"/>
+
+ <!-- default tag FragmentsBuilder -->
+ <fragmentsBuilder name="default"
+ default="true"
+ class="solr.highlight.ScoreOrderFragmentsBuilder">
+ <!--
+ <lst name="defaults">
+ <str name="hl.multiValuedSeparatorChar">/</str>
+ </lst>
+ -->
+ </fragmentsBuilder>
+
+ <!-- multi-colored tag FragmentsBuilder -->
+ <fragmentsBuilder name="colored"
+ class="solr.highlight.ScoreOrderFragmentsBuilder">
+ <lst name="defaults">
+ <str name="hl.tag.pre"><![CDATA[
+ <b style="background:yellow">,<b style="background:lawgreen">,
+ <b style="background:aquamarine">,<b style="background:magenta">,
+ <b style="background:palegreen">,<b style="background:coral">,
+ <b style="background:wheat">,<b style="background:khaki">,
+ <b style="background:lime">,<b style="background:deepskyblue">]]></str>
+ <str name="hl.tag.post"><![CDATA[</b>]]></str>
+ </lst>
+ </fragmentsBuilder>
+
+ <boundaryScanner name="default"
+ default="true"
+ class="solr.highlight.SimpleBoundaryScanner">
+ <lst name="defaults">
+ <str name="hl.bs.maxScan">10</str>
+ <str name="hl.bs.chars">.,!? &#9;&#10;&#13;</str>
+ </lst>
+ </boundaryScanner>
+
+ <boundaryScanner name="breakIterator"
+ class="solr.highlight.BreakIteratorBoundaryScanner">
+ <lst name="defaults">
+ <!-- type should be one of CHARACTER, WORD(default), LINE and SENTENCE -->
+ <str name="hl.bs.type">WORD</str>
+ <!-- language and country are used when constructing Locale object. -->
+ <!-- And the Locale object will be used when getting instance of BreakIterator -->
+ <str name="hl.bs.language">en</str>
+ <str name="hl.bs.country">US</str>
+ </lst>
+ </boundaryScanner>
+ </highlighting>
+ </searchComponent>
+
+ <!-- Update Processors
+
+ Chains of Update Processor Factories for dealing with Update
+ Requests can be declared, and then used by name in Update
+ Request Processors
+
+ http://wiki.apache.org/solr/UpdateRequestProcessor
+
+ -->
+
+ <!-- Add unknown fields to the schema
+
+ Field type guessing update processors that will
+ attempt to parse string-typed field values as Booleans, Longs,
+ Doubles, or Dates, and then add schema fields with the guessed
+ field types. Text content will be indexed as "text_general" as
+ well as a copy to a plain string version in *_str.
+
+ These require that the schema is both managed and mutable, by
+ declaring schemaFactory as ManagedIndexSchemaFactory, with
+ mutable specified as true.
+
+ See http://wiki.apache.org/solr/GuessingFieldTypes
+ -->
+ <updateProcessor class="solr.UUIDUpdateProcessorFactory" name="uuid"/>
+ <updateProcessor class="solr.RemoveBlankFieldUpdateProcessorFactory" name="remove-blank"/>
+ <updateProcessor class="solr.FieldNameMutatingUpdateProcessorFactory" name="field-name-mutating">
+ <str name="pattern">[^\w-\.]</str>
+ <str name="replacement">_</str>
+ </updateProcessor>
+ <updateProcessor class="solr.ParseBooleanFieldUpdateProcessorFactory" name="parse-boolean"/>
+ <updateProcessor class="solr.ParseLongFieldUpdateProcessorFactory" name="parse-long"/>
+ <updateProcessor class="solr.ParseDoubleFieldUpdateProcessorFactory" name="parse-double"/>
+ <updateProcessor class="solr.ParseDateFieldUpdateProcessorFactory" name="parse-date">
+ <arr name="format">
+ <str>yyyy-MM-dd['T'[HH:mm[:ss[.SSS]][z</str>
+ <str>yyyy-MM-dd['T'[HH:mm[:ss[,SSS]][z</str>
+ <str>yyyy-MM-dd HH:mm[:ss[.SSS]][z</str>
+ <str>yyyy-MM-dd HH:mm[:ss[,SSS]][z</str>
+ <str>[EEE, ]dd MMM yyyy HH:mm[:ss] z</str>
+ <str>EEEE, dd-MMM-yy HH:mm:ss z</str>
+ <str>EEE MMM ppd HH:mm:ss [z ]yyyy</str>
+ </arr>
+ </updateProcessor>
+ <updateProcessor class="solr.AddSchemaFieldsUpdateProcessorFactory" name="add-schema-fields">
+ <lst name="typeMapping">
+ <str name="valueClass">java.lang.String</str>
+ <str name="fieldType">text_general</str>
+ <lst name="copyField">
+ <str name="dest">*_str</str>
+ <int name="maxChars">256</int>
+ </lst>
+ <!-- Use as default mapping instead of defaultFieldType -->
+ <bool name="default">true</bool>
+ </lst>
+ <lst name="typeMapping">
+ <str name="valueClass">java.lang.Boolean</str>
+ <str name="fieldType">booleans</str>
+ </lst>
+ <lst name="typeMapping">
+ <str name="valueClass">java.util.Date</str>
+ <str name="fieldType">pdates</str>
+ </lst>
+ <lst name="typeMapping">
+ <str name="valueClass">java.lang.Long</str>
+ <str name="valueClass">java.lang.Integer</str>
+ <str name="fieldType">plongs</str>
+ </lst>
+ <lst name="typeMapping">
+ <str name="valueClass">java.lang.Number</str>
+ <str name="fieldType">pdoubles</str>
+ </lst>
+ </updateProcessor>
+
+ <!-- The update.autoCreateFields property can be turned to false to disable schemaless mode -->
+ <updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:true}"
+ processor="uuid,remove-blank,field-name-mutating,parse-boolean,parse-long,parse-double,parse-date,add-schema-fields">
+ <processor class="solr.LogUpdateProcessorFactory"/>
+ <processor class="solr.DistributedUpdateProcessorFactory"/>
+ <processor class="solr.RunUpdateProcessorFactory"/>
+ </updateRequestProcessorChain>
+
+ <!-- Deduplication
+
+ An example dedup update processor that creates the "id" field
+ on the fly based on the hash code of some other fields. This
+ example has overwriteDupes set to false since we are using the
+ id field as the signatureField and Solr will maintain
+ uniqueness based on that anyway.
+
+ -->
+ <!--
+ <updateRequestProcessorChain name="dedupe">
+ <processor class="solr.processor.SignatureUpdateProcessorFactory">
+ <bool name="enabled">true</bool>
+ <str name="signatureField">id</str>
+ <bool name="overwriteDupes">false</bool>
+ <str name="fields">name,features,cat</str>
+ <str name="signatureClass">solr.processor.Lookup3Signature</str>
+ </processor>
+ <processor class="solr.LogUpdateProcessorFactory" />
+ <processor class="solr.RunUpdateProcessorFactory" />
+ </updateRequestProcessorChain>
+ -->
+
+ <!-- Language identification
+
+ This example update chain identifies the language of the incoming
+ documents using the langid contrib. The detected language is
+ written to field language_s. No field name mapping is done.
+ The fields used for detection are text, title, subject and description,
+ making this example suitable for detecting languages form full-text
+ rich documents injected via ExtractingRequestHandler.
+ See more about langId at http://wiki.apache.org/solr/LanguageDetection
+ -->
+ <!--
+ <updateRequestProcessorChain name="langid">
+ <processor class="org.apache.solr.update.processor.TikaLanguageIdentifierUpdateProcessorFactory">
+ <str name="langid.fl">text,title,subject,description</str>
+ <str name="langid.langField">language_s</str>
+ <str name="langid.fallback">en</str>
+ </processor>
+ <processor class="solr.LogUpdateProcessorFactory" />
+ <processor class="solr.RunUpdateProcessorFactory" />
+ </updateRequestProcessorChain>
+ -->
+
+ <!-- Script update processor
+
+ This example hooks in an update processor implemented using JavaScript.
+
+ See more about the script update processor at http://wiki.apache.org/solr/ScriptUpdateProcessor
+ -->
+ <!--
+ <updateRequestProcessorChain name="script">
+ <processor class="solr.StatelessScriptUpdateProcessorFactory">
+ <str name="script">update-script.js</str>
+ <lst name="params">
+ <str name="config_param">example config parameter</str>
+ </lst>
+ </processor>
+ <processor class="solr.RunUpdateProcessorFactory" />
+ </updateRequestProcessorChain>
+ -->
+
+ <!-- Response Writers
+
+ http://wiki.apache.org/solr/QueryResponseWriter
+
+ Request responses will be written using the writer specified by
+ the 'wt' request parameter matching the name of a registered
+ writer.
+
+ The "default" writer is the default and will be used if 'wt' is
+ not specified in the request.
+ -->
+ <!-- The following response writers are implicitly configured unless
+ overridden...
+ -->
+ <!--
+ <queryResponseWriter name="xml"
+ default="true"
+ class="solr.XMLResponseWriter" />
+ <queryResponseWriter name="json" class="solr.JSONResponseWriter"/>
+ <queryResponseWriter name="python" class="solr.PythonResponseWriter"/>
+ <queryResponseWriter name="ruby" class="solr.RubyResponseWriter"/>
+ <queryResponseWriter name="php" class="solr.PHPResponseWriter"/>
+ <queryResponseWriter name="phps" class="solr.PHPSerializedResponseWriter"/>
+ <queryResponseWriter name="csv" class="solr.CSVResponseWriter"/>
+ <queryResponseWriter name="schema.xml" class="solr.SchemaXmlResponseWriter"/>
+ -->
+
+ <queryResponseWriter name="json" class="solr.JSONResponseWriter">
+ <!-- For the purposes of the tutorial, JSON responses are written as
+ plain text so that they are easy to read in *any* browser.
+ If you expect a MIME type of "application/json" just remove this override.
+ -->
+ <str name="content-type">text/plain; charset=UTF-8</str>
+ </queryResponseWriter>
+
+ <!--
+ Custom response writers can be declared as needed...
+ -->
+ <queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
+ <str name="template.base.dir">${velocity.template.base.dir:}</str>
+ <str name="solr.resource.loader.enabled">${velocity.solr.resource.loader.enabled:true}</str>
+ <str name="params.resource.loader.enabled">${velocity.params.resource.loader.enabled:false}</str>
+ </queryResponseWriter>
+
+ <!-- XSLT response writer transforms the XML output by any xslt file found
+ in Solr's conf/xslt directory. Changes to xslt files are checked for
+ every xsltCacheLifetimeSeconds.
+ -->
+ <queryResponseWriter name="xslt" class="solr.XSLTResponseWriter">
+ <int name="xsltCacheLifetimeSeconds">5</int>
+ </queryResponseWriter>
+
+ <!-- Query Parsers
+
+ https://lucene.apache.org/solr/guide/query-syntax-and-parsing.html
+
+ Multiple QParserPlugins can be registered by name, and then
+ used in either the "defType" param for the QueryComponent (used
+ by SearchHandler) or in LocalParams
+ -->
+ <!-- example of registering a query parser -->
+ <!--
+ <queryParser name="myparser" class="com.mycompany.MyQParserPlugin"/>
+ -->
+
+ <!-- Function Parsers
+
+ http://wiki.apache.org/solr/FunctionQuery
+
+ Multiple ValueSourceParsers can be registered by name, and then
+ used as function names when using the "func" QParser.
+ -->
+ <!-- example of registering a custom function parser -->
+ <!--
+ <valueSourceParser name="myfunc"
+ class="com.mycompany.MyValueSourceParser" />
+ -->
+
+
+ <!-- Document Transformers
+ http://wiki.apache.org/solr/DocTransformers
+ -->
+ <!--
+ Could be something like:
+ <transformer name="db" class="com.mycompany.LoadFromDatabaseTransformer" >
+ <int name="connection">jdbc://....</int>
+ </transformer>
+
+ To add a constant value to all docs, use:
+ <transformer name="mytrans2" class="org.apache.solr.response.transform.ValueAugmenterFactory" >
+ <int name="value">5</int>
+ </transformer>
+
+ If you want the user to still be able to change it with _value:something_ use this:
+ <transformer name="mytrans3" class="org.apache.solr.response.transform.ValueAugmenterFactory" >
+ <double name="defaultValue">5</double>
+ </transformer>
+
+ If you are using the QueryElevationComponent, you may wish to mark documents that get boosted. The
+ EditorialMarkerFactory will do exactly that:
+ <transformer name="qecBooster" class="org.apache.solr.response.transform.EditorialMarkerFactory" />
+ -->
+</config>
diff --git a/solr/conf/stopwords.txt b/solr/conf/stopwords.txt
new file mode 100644
index 000000000..ae1e83eeb
--- /dev/null
+++ b/solr/conf/stopwords.txt
@@ -0,0 +1,14 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/solr/conf/synonyms.txt b/solr/conf/synonyms.txt
new file mode 100644
index 000000000..eab4ee875
--- /dev/null
+++ b/solr/conf/synonyms.txt
@@ -0,0 +1,29 @@
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#-----------------------------------------------------------------------
+#some test synonym mappings unlikely to appear in real input text
+aaafoo => aaabar
+bbbfoo => bbbfoo bbbbar
+cccfoo => cccbar cccbaz
+fooaaa,baraaa,bazaaa
+
+# Some synonym groups specific to this example
+GB,gib,gigabyte,gigabytes
+MB,mib,megabyte,megabytes
+Television, Televisions, TV, TVs
+#notice we use "gib" instead of "GiB" so any WordDelimiterGraphFilter coming
+#after us won't split it into two words.
+
+# Synonym mappings can be used for spelling correction too
+pixima => pixma
+
diff --git a/src/.DS_Store b/src/.DS_Store
index f20f36d63..d70e95c0a 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index d4b7da52c..24878a368 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,24 +1,26 @@
import v4 = require('uuid/v4');
import v5 = require("uuid/v5");
import { Socket } from 'socket.io';
-import { Message, Types } from './server/Message';
+import { Message } from './server/Message';
export class Utils {
+ public static DRAG_THRESHOLD = 4;
+
public static GenerateGuid(): string {
- return v4()
+ return v4();
}
public static GenerateDeterministicGuid(seed: string): string {
- return v5(seed, v5.URL)
+ return v5(seed, v5.URL);
}
public static GetScreenTransform(ele: HTMLElement): { scale: number, translateX: number, translateY: number } {
if (!ele) {
- return { scale: 1, translateX: 1, translateY: 1 }
+ return { scale: 1, translateX: 1, translateY: 1 };
}
const rect = ele.getBoundingClientRect();
- const scale = ele.offsetWidth == 0 && rect.width == 0 ? 1 : rect.width / ele.offsetWidth;
+ const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth;
const translateX = rect.left;
const translateY = rect.top;
@@ -37,19 +39,96 @@ export class Utils {
document.body.removeChild(textArea);
}
+ public static loggingEnabled: Boolean = false;
+ public static logFilter: number | undefined = undefined;
+ private static log(prefix: string, messageName: string, message: any, receiving: boolean) {
+ if (!this.loggingEnabled) {
+ return;
+ }
+ message = message || {};
+ if (this.logFilter !== undefined && this.logFilter !== message.type) {
+ return;
+ }
+ let idString = (message.id || "").padStart(36, ' ');
+ prefix = prefix.padEnd(16, ' ');
+ console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)}`);
+ }
+ private static loggingCallback(prefix: string, func: (args: any) => any, messageName: string) {
+ return (args: any) => {
+ this.log(prefix, messageName, args, true);
+ func(args);
+ };
+ }
+
public static Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) {
+ this.log("Emit", message.Name, args, false);
socket.emit(message.Message, args);
}
- public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any) {
- socket.emit(message.Message, args, fn);
+ public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>;
+ public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
+ public static EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
+ this.log("Emit", message.Name, args, false);
+ if (fn) {
+ socket.emit(message.Message, args, this.loggingCallback('Receiving', fn, message.Name));
+ } else {
+ return new Promise<any>(res => socket.emit(message.Message, args, this.loggingCallback('Receiving', res, message.Name)));
+ }
}
- public static AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) {
- socket.on(message.Message, handler);
+ public static AddServerHandler<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, handler: (args: T) => any) {
+ socket.on(message.Message, this.loggingCallback('Incoming', handler, message.Name));
}
public static AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) {
- socket.on(message.Message, (arg: T, fn: (res: any) => any) => handler([arg, fn]));
+ socket.on(message.Message, (arg: T, fn: (res: any) => any) => {
+ this.log('S receiving', message.Name, arg, true);
+ handler([arg, this.loggingCallback('S sending', fn, message.Name)]);
+ });
+ }
+}
+
+export function OmitKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void): { omit: any, extract: any } {
+ const omit: any = { ...obj };
+ const extract: any = {};
+ keys.forEach(key => {
+ extract[key] = omit[key];
+ delete omit[key];
+ });
+ addKeyFunc && addKeyFunc(omit);
+ return { omit, extract };
+}
+
+export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) {
+ var dup: any = {};
+ keys.forEach(key => dup[key] = obj[key]);
+ addKeyFunc && addKeyFunc(dup);
+ return dup;
+}
+
+export function returnTrue() { return true; }
+
+export function returnFalse() { return false; }
+
+export function returnOne() { return 1; }
+
+export function returnZero() { return 0; }
+
+export function emptyFunction() { }
+
+export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
+
+export type Predicate<K, V> = (entry: [K, V]) => boolean;
+
+export function deepCopy<K, V>(source: Map<K, V>, predicate?: Predicate<K, V>) {
+ let deepCopy = new Map<K, V>();
+ let entries = source.entries(), next = entries.next();
+ while (!next.done) {
+ let entry = next.value;
+ if (!predicate || predicate(entry)) {
+ deepCopy.set(entry[0], entry[1]);
+ }
+ next = entries.next();
}
+ return deepCopy;
} \ No newline at end of file
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
new file mode 100644
index 000000000..a288d394a
--- /dev/null
+++ b/src/client/DocServer.ts
@@ -0,0 +1,130 @@
+import * as OpenSocket from 'socket.io-client';
+import { MessageStore } from "./../server/Message";
+import { Opt } from '../new_fields/Doc';
+import { Utils } from '../Utils';
+import { SerializationHelper } from './util/SerializationHelper';
+import { RefField, HandleUpdate, Id } from '../new_fields/RefField';
+
+export namespace DocServer {
+ const _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+ const _socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:4321`);
+ const GUID: string = Utils.GenerateGuid();
+
+ export function prepend(extension: string): string {
+ return window.location.origin + extension;
+ }
+
+ export function DeleteDatabase() {
+ Utils.Emit(_socket, MessageStore.DeleteAll, {});
+ }
+
+ export async function GetRefField(id: string): Promise<Opt<RefField>> {
+ let cached = _cache[id];
+ if (cached === undefined) {
+ const prom = Utils.EmitCallback(_socket, MessageStore.GetRefField, id).then(fieldJson => {
+ const field = SerializationHelper.Deserialize(fieldJson);
+ if (_cache[id] !== undefined && !(_cache[id] instanceof Promise)) {
+ id;
+ }
+ if (field !== undefined) {
+ _cache[id] = field;
+ } else {
+ delete _cache[id];
+ }
+ return field;
+ });
+ _cache[id] = prom;
+ return prom;
+ } else if (cached instanceof Promise) {
+ return cached;
+ } else {
+ return cached;
+ }
+ }
+
+ export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt<RefField> }> {
+ const requestedIds: string[] = [];
+ const waitingIds: string[] = [];
+ const promises: Promise<Opt<RefField>>[] = [];
+ const map: { [id: string]: Opt<RefField> } = {};
+ for (const id of ids) {
+ const cached = _cache[id];
+ if (cached === undefined) {
+ requestedIds.push(id);
+ } else if (cached instanceof Promise) {
+ promises.push(cached);
+ waitingIds.push(id);
+ } else {
+ map[id] = cached;
+ }
+ }
+ const prom = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds).then(fields => {
+ const fieldMap: { [id: string]: RefField } = {};
+ for (const field of fields) {
+ if (field !== undefined) {
+ fieldMap[field.id] = SerializationHelper.Deserialize(field);
+ }
+ }
+ return fieldMap;
+ });
+ requestedIds.forEach(id => _cache[id] = prom.then(fields => fields[id]));
+ const fields = await prom;
+ requestedIds.forEach(id => {
+ const field = fields[id];
+ if (field !== undefined) {
+ _cache[id] = field;
+ } else {
+ delete _cache[id];
+ }
+ map[id] = field;
+ });
+ const otherFields = await Promise.all(promises);
+ waitingIds.forEach((id, index) => map[id] = otherFields[index]);
+ return map;
+ }
+
+ export function UpdateField(id: string, diff: any) {
+ if (id === updatingId) {
+ return;
+ }
+ Utils.Emit(_socket, MessageStore.UpdateField, { id, diff });
+ }
+
+ export function CreateField(field: RefField) {
+ _cache[field[Id]] = field;
+ const initialState = SerializationHelper.Serialize(field);
+ Utils.Emit(_socket, MessageStore.CreateField, initialState);
+ }
+
+ let updatingId: string | undefined;
+ function respondToUpdate(diff: any) {
+ const id = diff.id;
+ if (id === undefined) {
+ return;
+ }
+ const field = _cache[id];
+ const update = (f: Opt<RefField>) => {
+ if (f === undefined) {
+ return;
+ }
+ const handler = f[HandleUpdate];
+ if (handler) {
+ updatingId = id;
+ handler.call(f, diff.diff);
+ updatingId = undefined;
+ }
+ };
+ if (field instanceof Promise) {
+ field.then(update);
+ } else {
+ update(field);
+ }
+ }
+
+ function connected() {
+ _socket.emit(MessageStore.Bar.Message, GUID);
+ }
+
+ Utils.AddServerHandler(_socket, MessageStore.Foo, connected);
+ Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
+} \ No newline at end of file
diff --git a/src/client/Server.ts b/src/client/Server.ts
deleted file mode 100644
index f0cf0bb9b..000000000
--- a/src/client/Server.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { Key } from "../fields/Key"
-import { ObservableMap, action, reaction } from "mobx";
-import { Field, FieldWaiting, FIELD_WAITING, Opt, FieldId } from "../fields/Field"
-import { Document } from "../fields/Document"
-import { SocketStub } from "./SocketStub";
-import * as OpenSocket from 'socket.io-client';
-import { Utils } from "./../Utils";
-import { MessageStore, Types } from "./../server/Message";
-
-export class Server {
- public static ClientFieldsCached: ObservableMap<FieldId, Field | FIELD_WAITING> = new ObservableMap();
- static Socket: SocketIOClient.Socket = OpenSocket(`${window.location.protocol}//${window.location.hostname}:1234`);
- static GUID: string = Utils.GenerateGuid()
-
-
- // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached).
- // Call this is from within a reaction and test whether the return value is FieldWaiting.
- // 'hackTimeout' is here temporarily for simplicity when debugging things.
- public static GetField(fieldid: FieldId, callback: (field: Opt<Field>) => void): Opt<Field> | FIELD_WAITING {
- let cached = this.ClientFieldsCached.get(fieldid);
- if (!cached) {
- this.ClientFieldsCached.set(fieldid, FieldWaiting);
- SocketStub.SEND_FIELD_REQUEST(fieldid, action((field: Field | undefined) => {
- let cached = this.ClientFieldsCached.get(fieldid);
- if (cached != FieldWaiting)
- callback(cached);
- else {
- if (field) {
- this.ClientFieldsCached.set(fieldid, field);
- } else {
- this.ClientFieldsCached.delete(fieldid)
- }
- callback(field)
- }
- }));
- } else if (cached != FieldWaiting) {
- setTimeout(() => callback(cached as Field), 0);
- } else {
- reaction(() => {
- return this.ClientFieldsCached.get(fieldid);
- }, (field, reaction) => {
- if (field !== "<Waiting>") {
- callback(field)
- reaction.dispose()
- }
- })
- }
- return cached;
- }
-
- public static GetFields(fieldIds: FieldId[], callback: (fields: { [id: string]: Field }) => any) {
- SocketStub.SEND_FIELDS_REQUEST(fieldIds, (fields) => {
- for (let key in fields) {
- let field = fields[key];
- if (!this.ClientFieldsCached.has(field.Id)) {
- this.ClientFieldsCached.set(field.Id, field)
- }
- }
- callback(fields)
- });
- }
-
- public static GetDocumentField(doc: Document, key: Key, callback?: (field: Field) => void) {
- let field = doc._proxies.get(key.Id);
- if (field) {
- this.GetField(field,
- action((fieldfromserver: Opt<Field>) => {
- if (fieldfromserver) {
- doc.fields.set(key.Id, { key, field: fieldfromserver });
- if (callback) {
- callback(fieldfromserver);
- }
- }
- }));
- }
- }
-
- public static AddDocument(document: Document) {
- SocketStub.SEND_ADD_DOCUMENT(document);
- }
- public static AddDocumentField(doc: Document, key: Key, value: Field) {
- console.log("Add doc field " + doc.Title + " " + key.Name + " fid " + value.Id + " " + value);
- SocketStub.SEND_ADD_DOCUMENT_FIELD(doc, key, value);
- }
- public static DeleteDocumentField(doc: Document, key: Key) {
- SocketStub.SEND_DELETE_DOCUMENT_FIELD(doc, key);
- }
-
- public static UpdateField(field: Field) {
- if (!this.ClientFieldsCached.has(field.Id)) {
- this.ClientFieldsCached.set(field.Id, field)
- }
- SocketStub.SEND_SET_FIELD(field);
- }
-
- static connected(message: string) {
- Server.Socket.emit(MessageStore.Bar.Message, Server.GUID);
- }
-
- @action
- private static cacheField(clientField: Field) {
- var cached = this.ClientFieldsCached.get(clientField.Id);
- if (!cached || cached == FieldWaiting) {
- this.ClientFieldsCached.set(clientField.Id, clientField);
- } else {
- // probably should overwrite the values within any field that was already here...
- }
- return this.ClientFieldsCached.get(clientField.Id) as Field;
- }
-
- @action
- static updateField(field: { _id: string, data: any, type: Types }) {
- if (Server.ClientFieldsCached.has(field._id)) {
- var f = Server.ClientFieldsCached.get(field._id);
- if (f && f != FieldWaiting) {
- f.UpdateFromServer(field.data);
- f.init(() => { });
- }
- }
- }
-}
-
-Server.Socket.on(MessageStore.Foo.Message, Server.connected);
-Server.Socket.on(MessageStore.SetField.Message, Server.updateField); \ No newline at end of file
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts
deleted file mode 100644
index 18df4ca0a..000000000
--- a/src/client/SocketStub.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import { Key } from "../fields/Key"
-import { Field, FieldId, Opt } from "../fields/Field"
-import { ObservableMap } from "mobx";
-import { Document } from "../fields/Document"
-import { MessageStore, DocumentTransfer } from "../server/Message";
-import { Utils } from "../Utils";
-import { Server } from "./Server";
-import { ServerUtils } from "../server/ServerUtil";
-
-export class SocketStub {
-
- static FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
- public static SEND_ADD_DOCUMENT(document: Document) {
-
- // Send a serialized version of the document to the server
- // ...SOCKET(ADD_DOCUMENT, serialied document)
-
- // server stores each document field in its repository of stored fields
- // document.fields.forEach((f, key) => this.FieldStore.set((f as Field).Id, f as Field));
- // let strippedDoc = new Document(document.Id);
- // document.fields.forEach((f, key) => {
- // if (f) {
- // // let args: SetFieldArgs = new SetFieldArgs(f.Id, f.GetValue())
- // let args: Transferable = f.ToJson()
- // Utils.Emit(Server.Socket, MessageStore.SetField, args)
- // }
- // })
-
- // // server stores stripped down document (w/ only field id proxies) in the field store
- // this.FieldStore.set(document.Id, new Document(document.Id));
- // document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key.Id, (f as Field).Id));
-
- console.log("sending " + document.Title);
- Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson()))
- }
-
- public static SEND_FIELD_REQUEST(fieldid: FieldId, callback: (field: Opt<Field>) => void) {
- if (fieldid) {
- Utils.EmitCallback(Server.Socket, MessageStore.GetField, fieldid, (field: any) => {
- if (field) {
- ServerUtils.FromJson(field).init(callback);
- } else {
- callback(undefined);
- }
- })
- }
- }
-
- public static SEND_FIELDS_REQUEST(fieldIds: FieldId[], callback: (fields: { [key: string]: Field }) => any) {
- Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => {
- let fieldMap: any = {};
- for (let field of fields) {
- fieldMap[field._id] = ServerUtils.FromJson(field);
- }
- callback(fieldMap)
- });
- }
-
- public static SEND_ADD_DOCUMENT_FIELD(doc: Document, key: Key, value: Field) {
-
- // Send a serialized version of the field to the server along with the
- // associated info of the document id and key where it is used.
-
- // ...SOCKET(ADD_DOCUMENT_FIELD, document id, key id, serialized field)
-
- // server updates its document to hold a proxy mapping from key => fieldId
- var document = this.FieldStore.get(doc.Id) as Document;
- if (document)
- document._proxies.set(key.Id, value.Id);
-
- // server adds the field to its repository of fields
- this.FieldStore.set(value.Id, value);
- // Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(doc.ToJson()))
- }
-
- public static SEND_DELETE_DOCUMENT_FIELD(doc: Document, key: Key) {
- // Send a request to delete the field stored under the specified key from the document
-
- // ...SOCKET(DELETE_DOCUMENT_FIELD, document id, key id)
-
- // Server removes the field id from the document's list of field proxies
- var document = this.FieldStore.get(doc.Id) as Document;
- if (document)
- document._proxies.delete(key.Id);
- }
-
- public static SEND_SET_FIELD(field: Field) {
- // Send a request to set the value of a field
-
- // ...SOCKET(SET_FIELD, field id, serialized field value)
-
- // Server updates the value of the field in its fieldstore
- Utils.Emit(Server.Socket, MessageStore.SetField, field.ToJson())
- }
-}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7b30dff98..ed260d42e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,203 +1,341 @@
-import { Document } from "../../fields/Document";
-import { Server } from "../Server";
-import { KeyStore } from "../../fields/KeyStore";
-import { TextField } from "../../fields/TextField";
-import { NumberField } from "../../fields/NumberField";
-import { ListField } from "../../fields/ListField";
+import { HistogramField } from "../northstar/dash-fields/HistogramField";
+import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
+import { HistogramOperation } from "../northstar/operations/HistogramOperation";
+import { CollectionPDFView } from "../views/collections/CollectionPDFView";
+import { CollectionVideoView } from "../views/collections/CollectionVideoView";
+import { CollectionView } from "../views/collections/CollectionView";
+import { CollectionViewType } from "../views/collections/CollectionBaseView";
+import { AudioBox } from "../views/nodes/AudioBox";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { ImageField } from "../../fields/ImageField";
import { ImageBox } from "../views/nodes/ImageBox";
-import { WebField } from "../../fields/WebField";
-import { WebBox } from "../views/nodes/WebBox";
-import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
-import { HtmlField } from "../../fields/HtmlField";
-import { Key } from "../../fields/Key"
-import { Field } from "../../fields/Field";
-import { KeyValueBox } from "../views/nodes/KeyValueBox"
-import { KVPField } from "../../fields/KVPField";
-import { VideoField } from "../../fields/VideoField"
-import { VideoBox } from "../views/nodes/VideoBox";
-import { AudioField } from "../../fields/AudioField";
-import { AudioBox } from "../views/nodes/AudioBox";
-import { PDFField } from "../../fields/PDFField";
+import { KeyValueBox } from "../views/nodes/KeyValueBox";
import { PDFBox } from "../views/nodes/PDFBox";
-import { CollectionPDFView } from "../views/collections/CollectionPDFView";
-import { RichTextField } from "../../fields/RichTextField";
+import { VideoBox } from "../views/nodes/VideoBox";
+import { WebBox } from "../views/nodes/WebBox";
+import { Gateway } from "../northstar/manager/Gateway";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { action } from "mobx";
+import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel";
+import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel";
+import { AggregateFunction } from "../northstar/model/idea/idea";
+import { Template } from "../views/Templates";
+import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
+import { IconBox } from "../views/nodes/IconBox";
+import { Field, Doc, Opt } from "../../new_fields/Doc";
+import { OmitKeys } from "../../Utils";
+import { ImageField, VideoField, AudioField, PdfField, WebField } from "../../new_fields/URLField";
+import { HtmlField } from "../../new_fields/HtmlField";
+import { List } from "../../new_fields/List";
+import { Cast } from "../../new_fields/Types";
+import { IconField } from "../../new_fields/IconField";
+import { listSpec } from "../../new_fields/Schema";
+import { DocServer } from "../DocServer";
+import { StrokeData, InkField } from "../../new_fields/InkField";
+import { dropActionType } from "../util/DragManager";
+import { DateField } from "../../new_fields/DateField";
+import { schema } from "prosemirror-schema-basic";
export interface DocumentOptions {
x?: number;
y?: number;
+ ink?: InkField;
width?: number;
height?: number;
nativeWidth?: number;
nativeHeight?: number;
title?: string;
- panx?: number;
- pany?: number;
+ panX?: number;
+ panY?: number;
+ page?: number;
scale?: number;
+ baseLayout?: string;
layout?: string;
- layoutKeys?: Key[];
+ templates?: List<string>;
viewType?: number;
+ backgroundColor?: string;
+ dropAction?: dropActionType;
+ backgroundLayout?: string;
+ curPage?: number;
+ documentText?: string;
+ borderRounding?: number;
+ schemaColumns?: List<string>;
+ dockingConfig?: string;
+ // [key: string]: Opt<Field>;
}
+const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
-export namespace Documents {
- let textProto: Document;
- let imageProto: Document;
- let webProto: Document;
- let collProto: Document;
- let kvpProto: Document;
- let videoProto: Document;
- let audioProto: Document;
- let pdfProto: Document;
+export namespace Docs {
+ let textProto: Doc;
+ let histoProto: Doc;
+ let imageProto: Doc;
+ let webProto: Doc;
+ let collProto: Doc;
+ let kvpProto: Doc;
+ let videoProto: Doc;
+ let audioProto: Doc;
+ let pdfProto: Doc;
+ let iconProto: Doc;
const textProtoId = "textProto";
+ const histoProtoId = "histoProto";
const pdfProtoId = "pdfProto";
const imageProtoId = "imageProto";
const webProtoId = "webProto";
const collProtoId = "collectionProto";
const kvpProtoId = "kvpProto";
- const videoProtoId = "videoProto"
+ const videoProtoId = "videoProto";
const audioProtoId = "audioProto";
+ const iconProtoId = "iconProto";
- export function initProtos(mainDocId: string, callback: (mainDoc?: Document) => void) {
- Server.GetFields([collProtoId, textProtoId, imageProtoId, mainDocId], (fields) => {
- collProto = fields[collProtoId] as Document;
- imageProto = fields[imageProtoId] as Document;
- textProto = fields[textProtoId] as Document;
- webProto = fields[webProtoId] as Document;
- kvpProto = fields[kvpProtoId] as Document;
- callback(fields[mainDocId] as Document)
+ export function initProtos(): Promise<void> {
+ return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => {
+ textProto = fields[textProtoId] as Doc || CreateTextPrototype();
+ histoProto = fields[histoProtoId] as Doc || CreateHistogramPrototype();
+ collProto = fields[collProtoId] as Doc || CreateCollectionPrototype();
+ imageProto = fields[imageProtoId] as Doc || CreateImagePrototype();
+ webProto = fields[webProtoId] as Doc || CreateWebPrototype();
+ kvpProto = fields[kvpProtoId] as Doc || CreateKVPPrototype();
+ videoProto = fields[videoProtoId] as Doc || CreateVideoPrototype();
+ audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype();
+ pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype();
+ iconProto = fields[iconProtoId] as Doc || CreateIconPrototype();
});
}
- function assignOptions(doc: Document, options: DocumentOptions): Document {
- if (options.x !== undefined) { doc.SetNumber(KeyStore.X, options.x); }
- if (options.y !== undefined) { doc.SetNumber(KeyStore.Y, options.y); }
- if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); }
- if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
- if (options.nativeWidth !== undefined) { doc.SetNumber(KeyStore.NativeWidth, options.nativeWidth); }
- if (options.nativeHeight !== undefined) { doc.SetNumber(KeyStore.NativeHeight, options.nativeHeight); }
- if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
- if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
- if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
- if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
- if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
- if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
- if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
- return doc;
- }
- function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
- return assignOptions(new Document(protoId), { ...options, title: title, layout: layout });
- }
- function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: T, ctor: { new(): U }, id?: string) {
- var deleg = doc.MakeDelegate(id);
- deleg.SetData(KeyStore.Data, value, ctor);
- return assignOptions(deleg, options);
- }
-
- function GetImagePrototype(): Document {
- if (!imageProto) {
- imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"),
- { x: 0, y: 0, nativeWidth: 300, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
- imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString());
- }
+
+ function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Doc {
+ return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout });
+ }
+ function SetInstanceOptions<U extends Field>(doc: Doc, options: DocumentOptions, value: U) {
+ const deleg = Doc.MakeDelegate(doc);
+ deleg.data = value;
+ return Doc.assign(deleg, options);
+ }
+ function SetDelegateOptions<U extends Field>(doc: Doc, options: DocumentOptions) {
+ const deleg = Doc.MakeDelegate(doc);
+ return Doc.assign(deleg, options);
+ }
+
+ function CreateImagePrototype(): Doc {
+ let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"),
+ { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0 });
return imageProto;
}
- function GetTextPrototype(): Document {
- return textProto ? textProto :
- textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
- }
- function GetPdfPrototype(): Document {
- if (!pdfProto) {
- pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("AnnotationsKey"),
- { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
- pdfProto.SetNumber(KeyStore.CurPage, 1);
- pdfProto.SetText(KeyStore.BackgroundLayout, PDFBox.LayoutString());
- }
+
+ function CreateHistogramPrototype(): Doc {
+ let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"),
+ { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString() });
+ return histoProto;
+ }
+ function CreateIconPrototype(): Doc {
+ let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
+ { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) });
+ return iconProto;
+ }
+ function CreateTextPrototype(): Doc {
+ let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" });
+ return textProto;
+ }
+ function CreatePdfPrototype(): Doc {
+ let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"),
+ { x: 0, y: 0, nativeWidth: 1200, width: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 });
return pdfProto;
}
- function GetWebPrototype(): Document {
- return webProto ? webProto :
- webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] });
+ function CreateWebPrototype(): Doc {
+ let webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 300 });
+ return webProto;
}
- function GetCollectionPrototype(): Document {
- return collProto ? collProto :
- collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("DataKey"),
- { panx: 0, pany: 0, scale: 1, layoutKeys: [KeyStore.Data] });
+ function CreateCollectionPrototype(): Doc {
+ let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"),
+ { panX: 0, panY: 0, scale: 1, width: 500, height: 500 });
+ return collProto;
}
- function GetKVPPrototype(): Document {
- return kvpProto ? kvpProto :
- kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+ function CreateKVPPrototype(): Doc {
+ let kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150 });
+ return kvpProto;
}
- function GetVideoPrototype(): Document {
- return videoProto ? videoProto :
- videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", VideoBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+ function CreateVideoPrototype(): Doc {
+ let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"),
+ { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0 });
+ return videoProto;
}
- function GetAudioPrototype(): Document {
- return audioProto ? audioProto :
- audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+ function CreateAudioPrototype(): Doc {
+ let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 150 });
+ return audioProto;
}
+ function CreateInstance(proto: Doc, data: Field, options: DocumentOptions) {
+ const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys);
+ if (!("author" in protoProps)) {
+ protoProps.author = CurrentUserUtils.email;
+ }
+ if (!("creationDate" in protoProps)) {
+ protoProps.creationDate = new DateField;
+ }
+ protoProps.isPrototype = true;
+
+ return SetDelegateOptions(SetInstanceOptions(proto, protoProps, data), delegateProps);
+ }
export function ImageDocument(url: string, options: DocumentOptions = {}) {
- let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
- new URL(url), ImageField);
- doc.SetText(KeyStore.Caption, "my caption...");
- doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
- doc.SetText(KeyStore.OverlayLayout, FixedCaption());
- return doc;
+ return CreateInstance(imageProto, new ImageField(new URL(url)), options);
+ // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
+ // [new URL(url), ImageField]);
+ // doc.SetText(KeyStore.Caption, "my caption...");
+ // doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
+ // doc.SetText(KeyStore.OverlayLayout, FixedCaption());
+ // return doc;
}
export function VideoDocument(url: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetVideoPrototype(), options, new URL(url), VideoField);
+ return CreateInstance(videoProto, new VideoField(new URL(url)), options);
}
export function AudioDocument(url: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetAudioPrototype(), options, new URL(url), AudioField);
+ return CreateInstance(audioProto, new AudioField(new URL(url)), options);
+ }
+
+ export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) {
+ return CreateInstance(histoProto, new HistogramField(histoOp), options);
}
export function TextDocument(options: DocumentOptions = {}) {
- return SetInstanceOptions(GetTextPrototype(), options, "", RichTextField);
+ return CreateInstance(textProto, "", options);
+ }
+ export function IconDocument(icon: string, options: DocumentOptions = {}) {
+ return CreateInstance(iconProto, new IconField(icon), options);
}
export function PdfDocument(url: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetPdfPrototype(), options, new URL(url), PDFField);
+ return CreateInstance(pdfProto, new PdfField(new URL(url)), options);
+ }
+
+ export async function DBDocument(url: string, options: DocumentOptions = {}) {
+ let schemaName = options.title ? options.title : "-no schema-";
+ let ctlog = await Gateway.Instance.GetSchema(url, schemaName);
+ if (ctlog && ctlog.schemas) {
+ let schema = ctlog.schemas[0];
+ let schemaDoc = Docs.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! });
+ let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []);
+ if (!schemaDocuments) {
+ return;
+ }
+ CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc);
+ const docs = schemaDocuments;
+ CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
+ DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
+ if (field instanceof Doc) {
+ docs.push(field);
+ } else {
+ var atmod = new ColumnAttributeModel(attr);
+ let histoOp = new HistogramOperation(schema.displayName!,
+ new AttributeTransformationModel(atmod, AggregateFunction.None),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count));
+ docs.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
+ }
+ }));
+ });
+ return schemaDoc;
+ }
+ return Docs.TreeDocument([], { width: 50, height: 100, title: schemaName });
}
export function WebDocument(url: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetWebPrototype(), options, new URL(url), WebField);
+ return CreateInstance(webProto, new WebField(new URL(url)), options);
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetWebPrototype(), options, html, HtmlField);
+ return CreateInstance(webProto, new HtmlField(html), options);
}
- export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
- return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Freeform }, documents, ListField, id)
+ export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
+ return CreateInstance(kvpProto, document, options);
}
- export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
- return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Schema }, documents, ListField, id)
+ export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, makePrototype: boolean = true) {
+ if (!makePrototype) {
+ return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents));
+ }
+ return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform });
}
- export function DockDocument(config: string, options: DocumentOptions, id?: string) {
- return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Docking }, config, TextField, id)
+ export function SchemaDocument(schemaColumns: string[], documents: Array<Doc>, options: DocumentOptions) {
+ return CreateInstance(collProto, new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema });
}
- export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
- var deleg = GetKVPPrototype().MakeDelegate(id);
- deleg.Set(KeyStore.Data, document);
- return assignOptions(deleg, options);
+ export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
+ return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree });
+ }
+ export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions) {
+ return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config });
+ }
+
+ export function CaptionDocument(doc: Doc) {
+ const captionDoc = Doc.MakeAlias(doc);
+ captionDoc.overlayLayout = FixedCaption();
+ captionDoc.width = Cast(doc.width, "number", 0);
+ captionDoc.height = Cast(doc.height, "number", 0);
+ return captionDoc;
}
// example of custom display string for an image that shows a caption.
function EmbeddedCaption() {
return `<div style="height:100%">
- <div style="position:relative; margin:auto; height:85%;" >`
+ <div style="position:relative; margin:auto; height:85%; width:85%;" >`
+ ImageBox.LayoutString() +
`</div>
<div style="position:relative; height:15%; text-align:center; ">`
- + FormattedTextBox.LayoutString("CaptionKey") +
+ + FormattedTextBox.LayoutString("caption") +
`</div>
- </div>` };
- function FixedCaption() {
+ </div>`;
+ }
+ export function FixedCaption(fieldName: string = "caption") {
return `<div style="position:absolute; height:30px; bottom:0; width:100%">
<div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
- + FormattedTextBox.LayoutString("CaptionKey") +
+ + FormattedTextBox.LayoutString(fieldName) +
`</div>
- </div>` };
+ </div>`;
+ }
+
+ function OuterCaption() {
+ return (`
+<div>
+ <div style="margin:auto; height:calc(100%); width:100%;">
+ {layout}
+ </div>
+ <div style="height:(100% + 25px); width:100%; position:absolute">
+ <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+ </div>
+</div>
+ `);
+ }
+ function InnerCaption() {
+ return (`
+ <div>
+ <div style="margin:auto; height:calc(100% - 25px); width:100%;">
+ {layout}
+ </div>
+ <div style="height:25px; width:100%; position:absolute">
+ <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+ </div>
+ </div>
+ `);
+ }
+
+ /*
+
+ this template requires an additional style setting on the collectionView-cont to make the layout relative
+
+.collectionView-cont {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+ */
+ function Percentaption() {
+ return (`
+ <div>
+ <div style="margin:auto; height:85%; width:85%;">
+ {layout}
+ </div>
+ <div style="height:15%; width:100%; position:absolute">
+ <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"caption"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+ </div>
+ </div>
+ `);
+ }
} \ No newline at end of file
diff --git a/src/client/goldenLayout.d.ts b/src/client/goldenLayout.d.ts
new file mode 100644
index 000000000..b50240563
--- /dev/null
+++ b/src/client/goldenLayout.d.ts
@@ -0,0 +1,3 @@
+
+declare const GoldenLayout: any;
+export = GoldenLayout; \ No newline at end of file
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
new file mode 100644
index 000000000..ab2bcefed
--- /dev/null
+++ b/src/client/goldenLayout.js
@@ -0,0 +1,5360 @@
+(function ($) {
+ var lm = { "config": {}, "container": {}, "controls": {}, "errors": {}, "items": {}, "utils": {} };
+ lm.utils.F = function () {
+ };
+
+ lm.utils.extend = function (subClass, superClass) {
+ subClass.prototype = lm.utils.createObject(superClass.prototype);
+ subClass.prototype.contructor = subClass;
+ };
+
+ lm.utils.createObject = function (prototype) {
+ if (typeof Object.create === 'function') {
+ return Object.create(prototype);
+ } else {
+ lm.utils.F.prototype = prototype;
+ return new lm.utils.F();
+ }
+ };
+
+ lm.utils.objectKeys = function (object) {
+ var keys, key;
+
+ if (typeof Object.keys === 'function') {
+ return Object.keys(object);
+ } else {
+ keys = [];
+ for (key in object) {
+ keys.push(key);
+ }
+ return keys;
+ }
+ };
+
+ lm.utils.getHashValue = function (key) {
+ var matches = location.hash.match(new RegExp(key + '=([^&]*)'));
+ return matches ? matches[1] : null;
+ };
+
+ lm.utils.getQueryStringParam = function (param) {
+ if (window.location.hash) {
+ return lm.utils.getHashValue(param);
+ } else if (!window.location.search) {
+ return null;
+ }
+
+ var keyValuePairs = window.location.search.substr(1).split('&'),
+ params = {},
+ pair,
+ i;
+
+ for (i = 0; i < keyValuePairs.length; i++) {
+ pair = keyValuePairs[i].split('=');
+ params[pair[0]] = pair[1];
+ }
+
+ return params[param] || null;
+ };
+
+ lm.utils.copy = function (target, source) {
+ for (var key in source) {
+ target[key] = source[key];
+ }
+ return target;
+ };
+
+ /**
+ * This is based on Paul Irish's shim, but looks quite odd in comparison. Why?
+ * Because
+ * a) it shouldn't affect the global requestAnimationFrame function
+ * b) it shouldn't pass on the time that has passed
+ *
+ * @param {Function} fn
+ *
+ * @returns {void}
+ */
+ lm.utils.animFrame = function (fn) {
+ return (window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ })(function () {
+ fn();
+ });
+ };
+
+ lm.utils.indexOf = function (needle, haystack) {
+ if (!(haystack instanceof Array)) {
+ throw new Error('Haystack is not an Array');
+ }
+
+ if (haystack.indexOf) {
+ return haystack.indexOf(needle);
+ } else {
+ for (var i = 0; i < haystack.length; i++) {
+ if (haystack[i] === needle) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ };
+
+ if (typeof /./ != 'function' && typeof Int8Array != 'object') {
+ lm.utils.isFunction = function (obj) {
+ return typeof obj == 'function' || false;
+ };
+ } else {
+ lm.utils.isFunction = function (obj) {
+ return toString.call(obj) === '[object Function]';
+ };
+ }
+
+ lm.utils.fnBind = function (fn, context, boundArgs) {
+
+ if (Function.prototype.bind !== undefined) {
+ return Function.prototype.bind.apply(fn, [context].concat(boundArgs || []));
+ }
+
+ var bound = function () {
+
+ // Join the already applied arguments to the now called ones (after converting to an array again).
+ var args = (boundArgs || []).concat(Array.prototype.slice.call(arguments, 0));
+
+ // If not being called as a constructor
+ if (!(this instanceof bound)) {
+ // return the result of the function called bound to target and partially applied.
+ return fn.apply(context, args);
+ }
+ // If being called as a constructor, apply the function bound to self.
+ fn.apply(this, args);
+ };
+ // Attach the prototype of the function to our newly created function.
+ bound.prototype = fn.prototype;
+ return bound;
+ };
+
+ lm.utils.removeFromArray = function (item, array) {
+ var index = lm.utils.indexOf(item, array);
+
+ if (index === -1) {
+ throw new Error('Can\'t remove item from array. Item is not in the array');
+ }
+
+ array.splice(index, 1);
+ };
+
+ lm.utils.now = function () {
+ if (typeof Date.now === 'function') {
+ return Date.now();
+ } else {
+ return (new Date()).getTime();
+ }
+ };
+
+ lm.utils.getUniqueId = function () {
+ return (Math.random() * 1000000000000000)
+ .toString(36)
+ .replace('.', '');
+ };
+
+ /**
+ * A basic XSS filter. It is ultimately up to the
+ * implementing developer to make sure their particular
+ * applications and usecases are save from cross site scripting attacks
+ *
+ * @param {String} input
+ * @param {Boolean} keepTags
+ *
+ * @returns {String} filtered input
+ */
+ lm.utils.filterXss = function (input, keepTags) {
+
+ var output = input
+ .replace(/javascript/gi, 'j&#97;vascript')
+ .replace(/expression/gi, 'expr&#101;ssion')
+ .replace(/onload/gi, 'onlo&#97;d')
+ .replace(/script/gi, '&#115;cript')
+ .replace(/onerror/gi, 'on&#101;rror');
+
+ if (keepTags === true) {
+ return output;
+ } else {
+ return output
+ .replace(/>/g, '&gt;')
+ .replace(/</g, '&lt;');
+ }
+ };
+
+ /**
+ * Removes html tags from a string
+ *
+ * @param {String} input
+ *
+ * @returns {String} input without tags
+ */
+ lm.utils.stripTags = function (input) {
+ return $.trim(input.replace(/(<([^>]+)>)/ig, ''));
+ };
+ /**
+ * A generic and very fast EventEmitter
+ * implementation. On top of emitting the
+ * actual event it emits an
+ *
+ * lm.utils.EventEmitter.ALL_EVENT
+ *
+ * event for every event triggered. This allows
+ * to hook into it and proxy events forwards
+ *
+ * @constructor
+ */
+ lm.utils.EventEmitter = function () {
+ this._mSubscriptions = {};
+ this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT] = [];
+
+ /**
+ * Listen for events
+ *
+ * @param {String} sEvent The name of the event to listen to
+ * @param {Function} fCallback The callback to execute when the event occurs
+ * @param {[Object]} oContext The value of the this pointer within the callback function
+ *
+ * @returns {void}
+ */
+ this.on = function (sEvent, fCallback, oContext) {
+ if (!lm.utils.isFunction(fCallback)) {
+ throw new Error('Tried to listen to event ' + sEvent + ' with non-function callback ' + fCallback);
+ }
+
+ if (!this._mSubscriptions[sEvent]) {
+ this._mSubscriptions[sEvent] = [];
+ }
+
+ this._mSubscriptions[sEvent].push({ fn: fCallback, ctx: oContext });
+ };
+
+ /**
+ * Emit an event and notify listeners
+ *
+ * @param {String} sEvent The name of the event
+ * @param {Mixed} various additional arguments that will be passed to the listener
+ *
+ * @returns {void}
+ */
+ this.emit = function (sEvent) {
+ var i, ctx, args;
+
+ args = Array.prototype.slice.call(arguments, 1);
+
+ var subs = this._mSubscriptions[sEvent];
+
+ if (subs) {
+ subs = subs.slice();
+ for (i = 0; i < subs.length; i++) {
+ ctx = subs[i].ctx || {};
+ subs[i].fn.apply(ctx, args);
+ }
+ }
+
+ args.unshift(sEvent);
+
+ var allEventSubs = this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT].slice()
+
+ for (i = 0; i < allEventSubs.length; i++) {
+ ctx = allEventSubs[i].ctx || {};
+ allEventSubs[i].fn.apply(ctx, args);
+ }
+ };
+
+ /**
+ * Removes a listener for an event, or all listeners if no callback and context is provided.
+ *
+ * @param {String} sEvent The name of the event
+ * @param {Function} fCallback The previously registered callback method (optional)
+ * @param {Object} oContext The previously registered context (optional)
+ *
+ * @returns {void}
+ */
+ this.unbind = function (sEvent, fCallback, oContext) {
+ if (!this._mSubscriptions[sEvent]) {
+ throw new Error('No subscribtions to unsubscribe for event ' + sEvent);
+ }
+
+ var i, bUnbound = false;
+
+ for (i = 0; i < this._mSubscriptions[sEvent].length; i++) {
+ if
+ (
+ (!fCallback || this._mSubscriptions[sEvent][i].fn === fCallback) &&
+ (!oContext || oContext === this._mSubscriptions[sEvent][i].ctx)
+ ) {
+ this._mSubscriptions[sEvent].splice(i, 1);
+ bUnbound = true;
+ }
+ }
+
+ if (bUnbound === false) {
+ throw new Error('Nothing to unbind for ' + sEvent);
+ }
+ };
+
+ /**
+ * Alias for unbind
+ */
+ this.off = this.unbind;
+
+ /**
+ * Alias for emit
+ */
+ this.trigger = this.emit;
+ };
+
+ /**
+ * The name of the event that's triggered for every other event
+ *
+ * usage
+ *
+ * myEmitter.on( lm.utils.EventEmitter.ALL_EVENT, function( eventName, argsArray ){
+ * //do stuff
+ * });
+ *
+ * @type {String}
+ */
+ lm.utils.EventEmitter.ALL_EVENT = '__all';
+ lm.utils.DragListener = function (eElement, nButtonCode) {
+ lm.utils.EventEmitter.call(this);
+
+ this._eElement = $(eElement);
+ this._oDocument = $(document);
+ this._eBody = $(document.body);
+ this._nButtonCode = nButtonCode || 0;
+
+ /**
+ * The delay after which to start the drag in milliseconds
+ */
+ this._nDelay = 200;
+
+ /**
+ * The distance the mouse needs to be moved to qualify as a drag
+ */
+ this._nDistance = 10;//TODO - works better with delay only
+
+ this._nX = 0;
+ this._nY = 0;
+
+ this._nOriginalX = 0;
+ this._nOriginalY = 0;
+
+ this._bDragging = false;
+
+ this._fMove = lm.utils.fnBind(this.onMouseMove, this);
+ this._fUp = lm.utils.fnBind(this.onMouseUp, this);
+ this._fDown = lm.utils.fnBind(this.onMouseDown, this);
+
+
+ this._eElement.on('mousedown touchstart', this._fDown);
+ };
+
+ lm.utils.DragListener.timeout = null;
+
+ lm.utils.copy(lm.utils.DragListener.prototype, {
+ destroy: function () {
+ this._eElement.unbind('mousedown touchstart', this._fDown);
+ this._oDocument.unbind('mouseup touchend', this._fUp);
+ this._eElement = null;
+ this._oDocument = null;
+ this._eBody = null;
+ },
+
+ onMouseDown: function (oEvent) {
+ oEvent.preventDefault();
+
+ if (oEvent.button == 0 || oEvent.type === "touchstart") {
+ var coordinates = this._getCoordinates(oEvent);
+
+ this._nOriginalX = coordinates.x;
+ this._nOriginalY = coordinates.y;
+
+ this._oDocument.on('mousemove touchmove', this._fMove);
+ this._oDocument.one('mouseup touchend', this._fUp);
+
+ this._timeout = setTimeout(lm.utils.fnBind(this._startDrag, this), this._nDelay);
+ }
+ },
+
+ onMouseMove: function (oEvent) {
+ if (this._timeout != null) {
+ oEvent.preventDefault();
+
+ var coordinates = this._getCoordinates(oEvent);
+
+ this._nX = coordinates.x - this._nOriginalX;
+ this._nY = coordinates.y - this._nOriginalY;
+
+ if (this._bDragging === false) {
+ if (
+ Math.abs(this._nX) > this._nDistance ||
+ Math.abs(this._nY) > this._nDistance
+ ) {
+ clearTimeout(this._timeout);
+ this._startDrag();
+ }
+ }
+
+ if (this._bDragging) {
+ this.emit('drag', this._nX, this._nY, oEvent);
+ }
+ }
+ },
+
+ onMouseUp: function (oEvent) {
+ if (this._timeout != null) {
+ clearTimeout(this._timeout);
+ this._eBody.removeClass('lm_dragging');
+ this._eElement.removeClass('lm_dragging');
+ this._oDocument.find('iframe').css('pointer-events', '');
+ this._oDocument.unbind('mousemove touchmove', this._fMove);
+ this._oDocument.unbind('mouseup touchend', this._fUp);
+
+ if (this._bDragging === true) {
+ this._bDragging = false;
+ this.emit('dragStop', oEvent, this._nOriginalX + this._nX);
+ }
+ }
+ },
+
+ _startDrag: function () {
+ this._bDragging = true;
+ this._eBody.addClass('lm_dragging');
+ this._eElement.addClass('lm_dragging');
+ this._oDocument.find('iframe').css('pointer-events', 'none');
+ this.emit('dragStart', this._nOriginalX, this._nOriginalY);
+ },
+
+ _getCoordinates: function (event) {
+ event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event;
+ return {
+ x: event.pageX,
+ y: event.pageY
+ };
+ }
+ });
+ /**
+ * The main class that will be exposed as GoldenLayout.
+ *
+ * @public
+ * @constructor
+ * @param {GoldenLayout config} config
+ * @param {[DOM element container]} container Can be a jQuery selector string or a Dom element. Defaults to body
+ *
+ * @returns {VOID}
+ */
+ lm.LayoutManager = function (config, container) {
+
+ if (!$ || typeof $.noConflict !== 'function') {
+ var errorMsg = 'jQuery is missing as dependency for GoldenLayout. ';
+ errorMsg += 'Please either expose $ on GoldenLayout\'s scope (e.g. window) or add "jquery" to ';
+ errorMsg += 'your paths when using RequireJS/AMD';
+ throw new Error(errorMsg);
+ }
+ lm.utils.EventEmitter.call(this);
+
+ this.isInitialised = false;
+ this._isFullPage = false;
+ this._resizeTimeoutId = null;
+ this._components = { 'lm-react-component': lm.utils.ReactComponentHandler };
+ this._itemAreas = [];
+ this._resizeFunction = lm.utils.fnBind(this._onResize, this);
+ this._unloadFunction = lm.utils.fnBind(this._onUnload, this);
+ this._maximisedItem = null;
+ this._maximisePlaceholder = $('<div class="lm_maximise_place"></div>');
+ this._creationTimeoutPassed = false;
+ this._subWindowsCreated = false;
+ this._dragSources = [];
+ this._updatingColumnsResponsive = false;
+ this._firstLoad = true;
+
+ this.width = null;
+ this.height = null;
+ this.root = null;
+ this.openPopouts = [];
+ this.selectedItem = null;
+ this.isSubWindow = false;
+ this.eventHub = new lm.utils.EventHub(this);
+ this.config = this._createConfig(config);
+ this.container = container;
+ this.dropTargetIndicator = null;
+ this.transitionIndicator = null;
+ this.tabDropPlaceholder = $('<div class="lm_drop_tab_placeholder"></div>');
+
+ if (this.isSubWindow === true) {
+ $('body').css('visibility', 'hidden');
+ }
+
+ this._typeToItem = {
+ 'column': lm.utils.fnBind(lm.items.RowOrColumn, this, [true]),
+ 'row': lm.utils.fnBind(lm.items.RowOrColumn, this, [false]),
+ 'stack': lm.items.Stack,
+ 'component': lm.items.Component
+ };
+ };
+
+ /**
+ * Hook that allows to access private classes
+ */
+ lm.LayoutManager.__lm = lm;
+
+ /**
+ * Takes a GoldenLayout configuration object and
+ * replaces its keys and values recursively with
+ * one letter codes
+ *
+ * @static
+ * @public
+ * @param {Object} config A GoldenLayout config object
+ *
+ * @returns {Object} minified config
+ */
+ lm.LayoutManager.minifyConfig = function (config) {
+ return (new lm.utils.ConfigMinifier()).minifyConfig(config);
+ };
+
+ /**
+ * Takes a configuration Object that was previously minified
+ * using minifyConfig and returns its original version
+ *
+ * @static
+ * @public
+ * @param {Object} minifiedConfig
+ *
+ * @returns {Object} the original configuration
+ */
+ lm.LayoutManager.unminifyConfig = function (config) {
+ return (new lm.utils.ConfigMinifier()).unminifyConfig(config);
+ };
+
+ lm.utils.copy(lm.LayoutManager.prototype, {
+
+ /**
+ * Register a component with the layout manager. If a configuration node
+ * of type component is reached it will look up componentName and create the
+ * associated component
+ *
+ * {
+ * type: "component",
+ * componentName: "EquityNewsFeed",
+ * componentState: { "feedTopic": "us-bluechips" }
+ * }
+ *
+ * @public
+ * @param {String} name
+ * @param {Function} constructor
+ *
+ * @returns {void}
+ */
+ registerComponent: function (name, constructor) {
+ if (typeof constructor !== 'function') {
+ throw new Error('Please register a constructor function');
+ }
+
+ if (this._components[name] !== undefined) {
+ throw new Error('Component ' + name + ' is already registered');
+ }
+
+ this._components[name] = constructor;
+ },
+
+ /**
+ * Creates a layout configuration object based on the the current state
+ *
+ * @public
+ * @returns {Object} GoldenLayout configuration
+ */
+ toConfig: function (root) {
+ var config, next, i;
+
+ if (this.isInitialised === false) {
+ throw new Error('Can\'t create config, layout not yet initialised');
+ }
+
+ if (root && !(root instanceof lm.items.AbstractContentItem)) {
+ throw new Error('Root must be a ContentItem');
+ }
+
+ /*
+ * settings & labels
+ */
+ config = {
+ settings: lm.utils.copy({}, this.config.settings),
+ dimensions: lm.utils.copy({}, this.config.dimensions),
+ labels: lm.utils.copy({}, this.config.labels)
+ };
+
+ /*
+ * Content
+ */
+ config.content = [];
+ next = function (configNode, item) {
+ var key, i;
+
+ for (key in item.config) {
+ if (key !== 'content') {
+ configNode[key] = item.config[key];
+ }
+ }
+
+ if (item.contentItems.length) {
+ configNode.content = [];
+
+ for (i = 0; i < item.contentItems.length; i++) {
+ configNode.content[i] = {};
+ next(configNode.content[i], item.contentItems[i]);
+ }
+ }
+ };
+
+ if (root) {
+ next(config, { contentItems: [root] });
+ } else {
+ next(config, this.root);
+ }
+
+ /*
+ * Retrieve config for subwindows
+ */
+ this._$reconcilePopoutWindows();
+ config.openPopouts = [];
+ for (i = 0; i < this.openPopouts.length; i++) {
+ config.openPopouts.push(this.openPopouts[i].toConfig());
+ }
+
+ /*
+ * Add maximised item
+ */
+ config.maximisedItemId = this._maximisedItem ? '__glMaximised' : null;
+ return config;
+ },
+
+ /**
+ * Returns a previously registered component
+ *
+ * @public
+ * @param {String} name The name used
+ *
+ * @returns {Function}
+ */
+ getComponent: function (name) {
+ if (this._components[name] === undefined) {
+ throw new lm.errors.ConfigurationError('Unknown component "' + name + '"');
+ }
+
+ return this._components[name];
+ },
+
+ /**
+ * Creates the actual layout. Must be called after all initial components
+ * are registered. Recurses through the configuration and sets up
+ * the item tree.
+ *
+ * If called before the document is ready it adds itself as a listener
+ * to the document.ready event
+ *
+ * @public
+ *
+ * @returns {void}
+ */
+ init: function () {
+
+ /**
+ * Create the popout windows straight away. If popouts are blocked
+ * an error is thrown on the same 'thread' rather than a timeout and can
+ * be caught. This also prevents any further initilisation from taking place.
+ */
+ if (this._subWindowsCreated === false) {
+ this._createSubWindows();
+ this._subWindowsCreated = true;
+ }
+
+
+ /**
+ * If the document isn't ready yet, wait for it.
+ */
+ if (document.readyState === 'loading' || document.body === null) {
+ $(document).ready(lm.utils.fnBind(this.init, this));
+ return;
+ }
+
+ /**
+ * If this is a subwindow, wait a few milliseconds for the original
+ * page's js calls to be executed, then replace the bodies content
+ * with GoldenLayout
+ */
+ if (this.isSubWindow === true && this._creationTimeoutPassed === false) {
+ setTimeout(lm.utils.fnBind(this.init, this), 7);
+ this._creationTimeoutPassed = true;
+ return;
+ }
+
+ if (this.isSubWindow === true) {
+ this._adjustToWindowMode();
+ }
+
+ this._setContainer();
+ this.dropTargetIndicator = new lm.controls.DropTargetIndicator(this.container);
+ this.transitionIndicator = new lm.controls.TransitionIndicator();
+ this.updateSize();
+ this._create(this.config);
+ this._bindEvents();
+ this.isInitialised = true;
+ this._adjustColumnsResponsive();
+ this.emit('initialised');
+ },
+
+ /**
+ * Updates the layout managers size
+ *
+ * @public
+ * @param {[int]} width height in pixels
+ * @param {[int]} height width in pixels
+ *
+ * @returns {void}
+ */
+ updateSize: function (width, height) {
+ if (arguments.length === 2) {
+ this.width = width;
+ this.height = height;
+ } else {
+ this.width = this.container.width();
+ this.height = this.container.height();
+ }
+
+ if (this.isInitialised === true) {
+ this.root.callDownwards('setSize', [this.width, this.height]);
+
+ if (this._maximisedItem) {
+ this._maximisedItem.element.width(this.container.width());
+ this._maximisedItem.element.height(this.container.height());
+ this._maximisedItem.callDownwards('setSize');
+ }
+
+ this._adjustColumnsResponsive();
+ }
+ },
+
+ /**
+ * Destroys the LayoutManager instance itself as well as every ContentItem
+ * within it. After this is called nothing should be left of the LayoutManager.
+ *
+ * @public
+ * @returns {void}
+ */
+ destroy: function () {
+ if (this.isInitialised === false) {
+ return;
+ }
+ this._onUnload();
+ $(window).off('resize', this._resizeFunction);
+ $(window).off('unload beforeunload', this._unloadFunction);
+ this.root.callDownwards('_$destroy', [], true);
+ this.root.contentItems = [];
+ this.tabDropPlaceholder.remove();
+ this.dropTargetIndicator.destroy();
+ this.transitionIndicator.destroy();
+ this.eventHub.destroy();
+
+ this._dragSources.forEach(function (dragSource) {
+ dragSource._dragListener.destroy();
+ dragSource._element = null;
+ dragSource._itemConfig = null;
+ dragSource._dragListener = null;
+ });
+ this._dragSources = [];
+ },
+
+ /**
+ * Recursively creates new item tree structures based on a provided
+ * ItemConfiguration object
+ *
+ * @public
+ * @param {Object} config ItemConfig
+ * @param {[ContentItem]} parent The item the newly created item should be a child of
+ *
+ * @returns {lm.items.ContentItem}
+ */
+ createContentItem: function (config, parent) {
+ var typeErrorMsg, contentItem;
+
+ if (typeof config.type !== 'string') {
+ throw new lm.errors.ConfigurationError('Missing parameter \'type\'', config);
+ }
+
+ if (config.type === 'react-component') {
+ config.type = 'component';
+ config.componentName = 'lm-react-component';
+ }
+
+ if (!this._typeToItem[config.type]) {
+ typeErrorMsg = 'Unknown type \'' + config.type + '\'. ' +
+ 'Valid types are ' + lm.utils.objectKeys(this._typeToItem).join(',');
+
+ throw new lm.errors.ConfigurationError(typeErrorMsg);
+ }
+
+
+ /**
+ * We add an additional stack around every component that's not within a stack anyways.
+ */
+ if (
+ // If this is a component
+ config.type === 'component' &&
+
+ // and it's not already within a stack
+ !(parent instanceof lm.items.Stack) &&
+
+ // and we have a parent
+ !!parent &&
+
+ // and it's not the topmost item in a new window
+ !(this.isSubWindow === true && parent instanceof lm.items.Root)
+ ) {
+ config = {
+ type: 'stack',
+ width: config.width,
+ height: config.height,
+ content: [config]
+ };
+ }
+
+ contentItem = new this._typeToItem[config.type](this, config, parent);
+ return contentItem;
+ },
+
+ /**
+ * Creates a popout window with the specified content and dimensions
+ *
+ * @param {Object|lm.itemsAbstractContentItem} configOrContentItem
+ * @param {[Object]} dimensions A map with width, height, left and top
+ * @param {[String]} parentId the id of the element this item will be appended to
+ * when popIn is called
+ * @param {[Number]} indexInParent The position of this item within its parent element
+
+ * @returns {lm.controls.BrowserPopout}
+ */
+ createPopout: function (configOrContentItem, dimensions, parentId, indexInParent) {
+ var config = configOrContentItem,
+ isItem = configOrContentItem instanceof lm.items.AbstractContentItem,
+ self = this,
+ windowLeft,
+ windowTop,
+ offset,
+ parent,
+ child,
+ browserPopout;
+
+ parentId = parentId || null;
+
+ if (isItem) {
+ config = this.toConfig(configOrContentItem).content;
+ parentId = lm.utils.getUniqueId();
+
+ /**
+ * If the item is the only component within a stack or for some
+ * other reason the only child of its parent the parent will be destroyed
+ * when the child is removed.
+ *
+ * In order to support this we move up the tree until we find something
+ * that will remain after the item is being popped out
+ */
+ parent = configOrContentItem.parent;
+ child = configOrContentItem;
+ while (parent.contentItems.length === 1 && !parent.isRoot) {
+ parent = parent.parent;
+ child = child.parent;
+ }
+
+ parent.addId(parentId);
+ if (isNaN(indexInParent)) {
+ indexInParent = lm.utils.indexOf(child, parent.contentItems);
+ }
+ } else {
+ if (!(config instanceof Array)) {
+ config = [config];
+ }
+ }
+
+
+ if (!dimensions && isItem) {
+ windowLeft = window.screenX || window.screenLeft;
+ windowTop = window.screenY || window.screenTop;
+ offset = configOrContentItem.element.offset();
+
+ dimensions = {
+ left: windowLeft + offset.left,
+ top: windowTop + offset.top,
+ width: configOrContentItem.element.width(),
+ height: configOrContentItem.element.height()
+ };
+ }
+
+ if (!dimensions && !isItem) {
+ dimensions = {
+ left: window.screenX || window.screenLeft + 20,
+ top: window.screenY || window.screenTop + 20,
+ width: 500,
+ height: 309
+ };
+ }
+
+ if (isItem) {
+ configOrContentItem.remove();
+ }
+
+ browserPopout = new lm.controls.BrowserPopout(config, dimensions, parentId, indexInParent, this);
+
+ browserPopout.on('initialised', function () {
+ self.emit('windowOpened', browserPopout);
+ });
+
+ browserPopout.on('closed', function () {
+ self._$reconcilePopoutWindows();
+ });
+
+ this.openPopouts.push(browserPopout);
+
+ return browserPopout;
+ },
+
+ /**
+ * Attaches DragListener to any given DOM element
+ * and turns it into a way of creating new ContentItems
+ * by 'dragging' the DOM element into the layout
+ *
+ * @param {jQuery DOM element} element
+ * @param {Object|Function} itemConfig for the new item to be created, or a function which will provide it
+ *
+ * @returns {void}
+ */
+ createDragSource: function (element, itemConfig) {
+ this.config.settings.constrainDragToContainer = false;
+ var dragSource = new lm.controls.DragSource($(element), itemConfig, this);
+ this._dragSources.push(dragSource);
+
+ return dragSource;
+ },
+
+ /**
+ * Programmatically selects an item. This deselects
+ * the currently selected item, selects the specified item
+ * and emits a selectionChanged event
+ *
+ * @param {lm.item.AbstractContentItem} item#
+ * @param {[Boolean]} _$silent Wheather to notify the item of its selection
+ * @event selectionChanged
+ *
+ * @returns {VOID}
+ */
+ selectItem: function (item, _$silent) {
+
+ if (this.config.settings.selectionEnabled !== true) {
+ throw new Error('Please set selectionEnabled to true to use this feature');
+ }
+
+ if (item === this.selectedItem) {
+ return;
+ }
+
+ if (this.selectedItem !== null) {
+ this.selectedItem.deselect();
+ }
+
+ if (item && _$silent !== true) {
+ item.select();
+ }
+
+ this.selectedItem = item;
+
+ this.emit('selectionChanged', item);
+ },
+
+ /*************************
+ * PACKAGE PRIVATE
+ *************************/
+ _$maximiseItem: function (contentItem) {
+ if (this._maximisedItem !== null) {
+ this._$minimiseItem(this._maximisedItem);
+ }
+ this._maximisedItem = contentItem;
+ this._maximisedItem.addId('__glMaximised');
+ contentItem.element.addClass('lm_maximised');
+ contentItem.element.after(this._maximisePlaceholder);
+ this.root.element.prepend(contentItem.element);
+ contentItem.element.width(this.container.width());
+ contentItem.element.height(this.container.height());
+ contentItem.callDownwards('setSize');
+ this._maximisedItem.emit('maximised');
+ this.emit('stateChanged');
+ },
+
+ _$minimiseItem: function (contentItem) {
+ contentItem.element.removeClass('lm_maximised');
+ contentItem.removeId('__glMaximised');
+ this._maximisePlaceholder.after(contentItem.element);
+ this._maximisePlaceholder.remove();
+ contentItem.parent.callDownwards('setSize');
+ this._maximisedItem = null;
+ contentItem.emit('minimised');
+ this.emit('stateChanged');
+ },
+
+ /**
+ * This method is used to get around sandboxed iframe restrictions.
+ * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute
+ * (as is the case with codepens) the parent window is forbidden from calling certain
+ * methods on the child, such as window.close() or setting document.location.href.
+ *
+ * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call
+ * _$closeWindow on the child window's gl instance which (after a timeout to disconnect
+ * the invoking method from the close call) closes itself.
+ *
+ * @packagePrivate
+ *
+ * @returns {void}
+ */
+ _$closeWindow: function () {
+ window.setTimeout(function () {
+ window.close();
+ }, 1);
+ },
+
+ _$getArea: function (x, y) {
+ var i, area, smallestSurface = Infinity, mathingArea = null;
+
+ for (i = 0; i < this._itemAreas.length; i++) {
+ area = this._itemAreas[i];
+
+ if (
+ x > area.x1 &&
+ x < area.x2 &&
+ y > area.y1 &&
+ y < area.y2 &&
+ smallestSurface > area.surface
+ ) {
+ smallestSurface = area.surface;
+ mathingArea = area;
+ }
+ }
+
+ return mathingArea;
+ },
+
+ _$createRootItemAreas: function () {
+ var areaSize = 50;
+ var sides = { y2: 0, x2: 0, y1: 'y2', x1: 'x2' };
+ for (var side in sides) {
+ var area = this.root._$getArea();
+ area.side = side;
+ if (sides[side])
+ area[side] = area[sides[side]] - areaSize;
+ else
+ area[side] = areaSize;
+ area.surface = (area.x2 - area.x1) * (area.y2 - area.y1);
+ this._itemAreas.push(area);
+ }
+ },
+
+ _$calculateItemAreas: function () {
+ var i, area, allContentItems = this._getAllContentItems();
+ this._itemAreas = [];
+
+ /**
+ * If the last item is dragged out, highlight the entire container size to
+ * allow to re-drop it. allContentItems[ 0 ] === this.root at this point
+ *
+ * Don't include root into the possible drop areas though otherwise since it
+ * will used for every gap in the layout, e.g. splitters
+ */
+ if (allContentItems.length === 1) {
+ this._itemAreas.push(this.root._$getArea());
+ return;
+ }
+ this._$createRootItemAreas();
+
+ for (i = 0; i < allContentItems.length; i++) {
+
+ if (!(allContentItems[i].isStack)) {
+ continue;
+ }
+
+ area = allContentItems[i]._$getArea();
+
+ if (area === null) {
+ continue;
+ } else if (area instanceof Array) {
+ this._itemAreas = this._itemAreas.concat(area);
+ } else {
+ this._itemAreas.push(area);
+ var header = {};
+ lm.utils.copy(header, area);
+ lm.utils.copy(header, area.contentItem._contentAreaDimensions.header.highlightArea);
+ header.surface = (header.x2 - header.x1) * (header.y2 - header.y1);
+ this._itemAreas.push(header);
+ }
+ }
+ },
+
+ /**
+ * Takes a contentItem or a configuration and optionally a parent
+ * item and returns an initialised instance of the contentItem.
+ * If the contentItem is a function, it is first called
+ *
+ * @packagePrivate
+ *
+ * @param {lm.items.AbtractContentItem|Object|Function} contentItemOrConfig
+ * @param {lm.items.AbtractContentItem} parent Only necessary when passing in config
+ *
+ * @returns {lm.items.AbtractContentItem}
+ */
+ _$normalizeContentItem: function (contentItemOrConfig, parent) {
+ if (!contentItemOrConfig) {
+ throw new Error('No content item defined');
+ }
+
+ if (lm.utils.isFunction(contentItemOrConfig)) {
+ contentItemOrConfig = contentItemOrConfig();
+ }
+
+ if (contentItemOrConfig instanceof lm.items.AbstractContentItem) {
+ return contentItemOrConfig;
+ }
+
+ if ($.isPlainObject(contentItemOrConfig) && contentItemOrConfig.type) {
+ var newContentItem = this.createContentItem(contentItemOrConfig, parent);
+ newContentItem.callDownwards('_$init');
+ return newContentItem;
+ } else {
+ throw new Error('Invalid contentItem');
+ }
+ },
+
+ /**
+ * Iterates through the array of open popout windows and removes the ones
+ * that are effectively closed. This is necessary due to the lack of reliably
+ * listening for window.close / unload events in a cross browser compatible fashion.
+ *
+ * @packagePrivate
+ *
+ * @returns {void}
+ */
+ _$reconcilePopoutWindows: function () {
+ var openPopouts = [], i;
+
+ for (i = 0; i < this.openPopouts.length; i++) {
+ if (this.openPopouts[i].getWindow().closed === false) {
+ openPopouts.push(this.openPopouts[i]);
+ } else {
+ this.emit('windowClosed', this.openPopouts[i]);
+ }
+ }
+
+ if (this.openPopouts.length !== openPopouts.length) {
+ this.emit('stateChanged');
+ this.openPopouts = openPopouts;
+ }
+
+ },
+
+ /***************************
+ * PRIVATE
+ ***************************/
+ /**
+ * Returns a flattened array of all content items,
+ * regardles of level or type
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _getAllContentItems: function () {
+ var allContentItems = [];
+
+ var addChildren = function (contentItem) {
+ allContentItems.push(contentItem);
+
+ if (contentItem.contentItems instanceof Array) {
+ for (var i = 0; i < contentItem.contentItems.length; i++) {
+ addChildren(contentItem.contentItems[i]);
+ }
+ }
+ };
+
+ addChildren(this.root);
+
+ return allContentItems;
+ },
+
+ /**
+ * Binds to DOM/BOM events on init
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _bindEvents: function () {
+ if (this._isFullPage) {
+ $(window).resize(this._resizeFunction);
+ }
+ $(window).on('unload beforeunload', this._unloadFunction);
+ },
+
+ /**
+ * Debounces resize events
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _onResize: function () {
+ clearTimeout(this._resizeTimeoutId);
+ this._resizeTimeoutId = setTimeout(lm.utils.fnBind(this.updateSize, this), 100);
+ },
+
+ /**
+ * Extends the default config with the user specific settings and applies
+ * derivations. Please note that there's a seperate method (AbstractContentItem._extendItemNode)
+ * that deals with the extension of item configs
+ *
+ * @param {Object} config
+ * @static
+ * @returns {Object} config
+ */
+ _createConfig: function (config) {
+ var windowConfigKey = lm.utils.getQueryStringParam('gl-window');
+
+ if (windowConfigKey) {
+ this.isSubWindow = true;
+ config = localStorage.getItem(windowConfigKey);
+ config = JSON.parse(config);
+ config = (new lm.utils.ConfigMinifier()).unminifyConfig(config);
+ localStorage.removeItem(windowConfigKey);
+ }
+
+ config = $.extend(true, {}, lm.config.defaultConfig, config);
+
+ var nextNode = function (node) {
+ for (var key in node) {
+ if (key !== 'props' && typeof node[key] === 'object') {
+ nextNode(node[key]);
+ }
+ else if (key === 'type' && node[key] === 'react-component') {
+ node.type = 'component';
+ node.componentName = 'lm-react-component';
+ }
+ }
+ }
+
+ nextNode(config);
+
+ if (config.settings.hasHeaders === false) {
+ config.dimensions.headerHeight = 0;
+ }
+
+ return config;
+ },
+
+ /**
+ * This is executed when GoldenLayout detects that it is run
+ * within a previously opened popout window.
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _adjustToWindowMode: function () {
+ var popInButton = $('<div class="lm_popin" title="' + this.config.labels.popin + '">' +
+ '<div class="lm_icon"></div>' +
+ '<div class="lm_bg"></div>' +
+ '</div>');
+
+ popInButton.click(lm.utils.fnBind(function () {
+ this.emit('popIn');
+ }, this));
+
+ document.title = lm.utils.stripTags(this.config.content[0].title);
+
+ $('head').append($('body link, body style, template, .gl_keep'));
+
+ this.container = $('body')
+ .html('')
+ .css('visibility', 'visible')
+ .append(popInButton);
+
+ /*
+ * This seems a bit pointless, but actually causes a reflow/re-evaluation getting around
+ * slickgrid's "Cannot find stylesheet." bug in chrome
+ */
+ var x = document.body.offsetHeight; // jshint ignore:line
+
+ /*
+ * Expose this instance on the window object
+ * to allow the opening window to interact with
+ * it
+ */
+ window.__glInstance = this;
+ },
+
+ /**
+ * Creates Subwindows (if there are any). Throws an error
+ * if popouts are blocked.
+ *
+ * @returns {void}
+ */
+ _createSubWindows: function () {
+ var i, popout;
+
+ for (i = 0; i < this.config.openPopouts.length; i++) {
+ popout = this.config.openPopouts[i];
+
+ this.createPopout(
+ popout.content,
+ popout.dimensions,
+ popout.parentId,
+ popout.indexInParent
+ );
+ }
+ },
+
+ /**
+ * Determines what element the layout will be created in
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _setContainer: function () {
+ var container = $(this.container || 'body');
+
+ if (container.length === 0) {
+ throw new Error('GoldenLayout container not found');
+ }
+
+ if (container.length > 1) {
+ throw new Error('GoldenLayout more than one container element specified');
+ }
+
+ if (container[0] === document.body) {
+ this._isFullPage = true;
+
+ $('html, body').css({
+ height: '100%',
+ margin: 0,
+ padding: 0,
+ overflow: 'hidden'
+ });
+ }
+
+ this.container = container;
+ },
+
+ /**
+ * Kicks of the initial, recursive creation chain
+ *
+ * @param {Object} config GoldenLayout Config
+ *
+ * @returns {void}
+ */
+ _create: function (config) {
+ var errorMsg;
+
+ if (!(config.content instanceof Array)) {
+ if (config.content === undefined) {
+ errorMsg = 'Missing setting \'content\' on top level of configuration';
+ } else {
+ errorMsg = 'Configuration parameter \'content\' must be an array';
+ }
+
+ throw new lm.errors.ConfigurationError(errorMsg, config);
+ }
+
+ if (config.content.length > 1) {
+ errorMsg = 'Top level content can\'t contain more then one element.';
+ throw new lm.errors.ConfigurationError(errorMsg, config);
+ }
+
+ this.root = new lm.items.Root(this, { content: config.content }, this.container);
+ this.root.callDownwards('_$init');
+
+ if (config.maximisedItemId === '__glMaximised') {
+ this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise();
+ }
+ },
+
+ /**
+ * Called when the window is closed or the user navigates away
+ * from the page
+ *
+ * @returns {void}
+ */
+ _onUnload: function () {
+ if (this.config.settings.closePopoutsOnUnload === true) {
+ for (var i = 0; i < this.openPopouts.length; i++) {
+ this.openPopouts[i].close();
+ }
+ }
+ },
+
+ /**
+ * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth.
+ *
+ * @returns {void}
+ */
+ _adjustColumnsResponsive: function () {
+
+ // If there is no min width set, or not content items, do nothing.
+ if (!this._useResponsiveLayout() || this._updatingColumnsResponsive || !this.config.dimensions || !this.config.dimensions.minItemWidth || this.root.contentItems.length === 0 || !this.root.contentItems[0].isRow) {
+ this._firstLoad = false;
+ return;
+ }
+
+ this._firstLoad = false;
+
+ // If there is only one column, do nothing.
+ var columnCount = this.root.contentItems[0].contentItems.length;
+ if (columnCount <= 1) {
+ return;
+ }
+
+ // If they all still fit, do nothing.
+ var minItemWidth = this.config.dimensions.minItemWidth;
+ var totalMinWidth = columnCount * minItemWidth;
+ if (totalMinWidth <= this.width) {
+ return;
+ }
+
+ // Prevent updates while it is already happening.
+ this._updatingColumnsResponsive = true;
+
+ // Figure out how many columns to stack, and put them all in the first stack container.
+ var finalColumnCount = Math.max(Math.floor(this.width / minItemWidth), 1);
+ var stackColumnCount = columnCount - finalColumnCount;
+
+ var rootContentItem = this.root.contentItems[0];
+ var firstStackContainer = this._findAllStackContainers()[0];
+ for (var i = 0; i < stackColumnCount; i++) {
+ // Stack from right.
+ var column = rootContentItem.contentItems[rootContentItem.contentItems.length - 1];
+ this._addChildContentItemsToContainer(firstStackContainer, column);
+ }
+
+ this._updatingColumnsResponsive = false;
+ },
+
+ /**
+ * Determines if responsive layout should be used.
+ *
+ * @returns {bool} - True if responsive layout should be used; otherwise false.
+ */
+ _useResponsiveLayout: function () {
+ return this.config.settings && (this.config.settings.responsiveMode == 'always' || (this.config.settings.responsiveMode == 'onload' && this._firstLoad));
+ },
+
+ /**
+ * Adds all children of a node to another container recursively.
+ * @param {object} container - Container to add child content items to.
+ * @param {object} node - Node to search for content items.
+ * @returns {void}
+ */
+ _addChildContentItemsToContainer: function (container, node) {
+ if (node.type === 'stack') {
+ node.contentItems.forEach(function (item) {
+ container.addChild(item);
+ node.removeChild(item, true);
+ });
+ }
+ else {
+ node.contentItems.forEach(lm.utils.fnBind(function (item) {
+ this._addChildContentItemsToContainer(container, item);
+ }, this));
+ }
+ },
+
+ /**
+ * Finds all the stack containers.
+ * @returns {array} - The found stack containers.
+ */
+ _findAllStackContainers: function () {
+ var stackContainers = [];
+ this._findAllStackContainersRecursive(stackContainers, this.root);
+
+ return stackContainers;
+ },
+
+ /**
+ * Finds all the stack containers.
+ *
+ * @param {array} - Set of containers to populate.
+ * @param {object} - Current node to process.
+ *
+ * @returns {void}
+ */
+ _findAllStackContainersRecursive: function (stackContainers, node) {
+ node.contentItems.forEach(lm.utils.fnBind(function (item) {
+ if (item.type == 'stack') {
+ stackContainers.push(item);
+ }
+ else if (!item.isComponent) {
+ this._findAllStackContainersRecursive(stackContainers, item);
+ }
+ }, this));
+ }
+ });
+
+ /**
+ * Expose the Layoutmanager as the single entrypoint using UMD
+ */
+ (function () {
+ /* global define */
+ if (typeof define === 'function' && define.amd) {
+ define(['jquery'], function (jquery) {
+ $ = jquery;
+ return lm.LayoutManager;
+ }); // jshint ignore:line
+ } else if (typeof exports === 'object') {
+ module.exports = lm.LayoutManager;
+ } else {
+ window.GoldenLayout = lm.LayoutManager;
+ }
+ })();
+
+ lm.config.itemDefaultConfig = {
+ isClosable: true,
+ reorderEnabled: true,
+ title: ''
+ };
+ lm.config.defaultConfig = {
+ openPopouts: [],
+ settings: {
+ hasHeaders: true,
+ constrainDragToContainer: true,
+ reorderEnabled: true,
+ selectionEnabled: false,
+ popoutWholeStack: false,
+ blockedPopoutsThrowError: true,
+ closePopoutsOnUnload: true,
+ showPopoutIcon: true,
+ showMaximiseIcon: true,
+ showCloseIcon: true,
+ responsiveMode: 'onload', // Can be onload, always, or none.
+ tabOverlapAllowance: 0, // maximum pixel overlap per tab
+ reorderOnTabMenuClick: true,
+ tabControlOffset: 10
+ },
+ dimensions: {
+ borderWidth: 5,
+ borderGrabWidth: 15,
+ minItemHeight: 10,
+ minItemWidth: 10,
+ headerHeight: 20,
+ dragProxyWidth: 300,
+ dragProxyHeight: 200
+ },
+ labels: {
+ close: 'close',
+ maximise: 'maximise',
+ minimise: 'minimise',
+ popout: 'open in new window',
+ popin: 'pop in',
+ tabDropdown: 'additional tabs'
+ }
+ };
+
+ lm.container.ItemContainer = function (config, parent, layoutManager) {
+ lm.utils.EventEmitter.call(this);
+
+ this.width = null;
+ this.height = null;
+ this.title = config.componentName;
+ this.parent = parent;
+ this.layoutManager = layoutManager;
+ this.isHidden = false;
+
+ this._config = config;
+ this._element = $([
+ '<div class="lm_item_container">',
+ '<div class="lm_content"></div>',
+ '</div>'
+ ].join(''));
+
+ this._contentElement = this._element.find('.lm_content');
+ };
+
+ lm.utils.copy(lm.container.ItemContainer.prototype, {
+
+ /**
+ * Get the inner DOM element the container's content
+ * is intended to live in
+ *
+ * @returns {DOM element}
+ */
+ getElement: function () {
+ return this._contentElement;
+ },
+
+ /**
+ * Hide the container. Notifies the containers content first
+ * and then hides the DOM node. If the container is already hidden
+ * this should have no effect
+ *
+ * @returns {void}
+ */
+ hide: function () {
+ this.emit('hide');
+ this.isHidden = true;
+ this._element.hide();
+ },
+
+ /**
+ * Shows a previously hidden container. Notifies the
+ * containers content first and then shows the DOM element.
+ * If the container is already visible this has no effect.
+ *
+ * @returns {void}
+ */
+ show: function () {
+ this.emit('show');
+ this.isHidden = false;
+ this._element.show();
+ // call shown only if the container has a valid size
+ if (this.height != 0 || this.width != 0) {
+ this.emit('shown');
+ }
+ },
+
+ /**
+ * Set the size from within the container. Traverses up
+ * the item tree until it finds a row or column element
+ * and resizes its items accordingly.
+ *
+ * If this container isn't a descendant of a row or column
+ * it returns false
+ * @todo Rework!!!
+ * @param {Number} width The new width in pixel
+ * @param {Number} height The new height in pixel
+ *
+ * @returns {Boolean} resizeSuccesful
+ */
+ setSize: function (width, height) {
+ var rowOrColumn = this.parent,
+ rowOrColumnChild = this,
+ totalPixel,
+ percentage,
+ direction,
+ newSize,
+ delta,
+ i;
+
+ while (!rowOrColumn.isColumn && !rowOrColumn.isRow) {
+ rowOrColumnChild = rowOrColumn;
+ rowOrColumn = rowOrColumn.parent;
+
+
+ /**
+ * No row or column has been found
+ */
+ if (rowOrColumn.isRoot) {
+ return false;
+ }
+ }
+
+ direction = rowOrColumn.isColumn ? "height" : "width";
+ newSize = direction === "height" ? height : width;
+
+ totalPixel = this[direction] * (1 / (rowOrColumnChild.config[direction] / 100));
+ percentage = (newSize / totalPixel) * 100;
+ delta = (rowOrColumnChild.config[direction] - percentage) / (rowOrColumn.contentItems.length - 1);
+
+ for (i = 0; i < rowOrColumn.contentItems.length; i++) {
+ if (rowOrColumn.contentItems[i] === rowOrColumnChild) {
+ rowOrColumn.contentItems[i].config[direction] = percentage;
+ } else {
+ rowOrColumn.contentItems[i].config[direction] += delta;
+ }
+ }
+
+ rowOrColumn.callDownwards('setSize');
+
+ return true;
+ },
+
+ /**
+ * Closes the container if it is closable. Can be called by
+ * both the component within at as well as the contentItem containing
+ * it. Emits a close event before the container itself is closed.
+ *
+ * @returns {void}
+ */
+ close: function () {
+ if (this._config.isClosable) {
+ this.emit('close');
+ this.parent.close();
+ }
+ },
+
+ /**
+ * Returns the current state object
+ *
+ * @returns {Object} state
+ */
+ getState: function () {
+ return this._config.componentState;
+ },
+
+ /**
+ * Merges the provided state into the current one
+ *
+ * @param {Object} state
+ *
+ * @returns {void}
+ */
+ extendState: function (state) {
+ this.setState($.extend(true, this.getState(), state));
+ },
+
+ /**
+ * Notifies the layout manager of a stateupdate
+ *
+ * @param {serialisable} state
+ */
+ setState: function (state) {
+ this._config.componentState = state;
+ this.parent.emitBubblingEvent('stateChanged');
+ },
+
+ /**
+ * Set's the components title
+ *
+ * @param {String} title
+ */
+ setTitle: function (title) {
+ this.parent.setTitle(title);
+ },
+
+ /**
+ * Set's the containers size. Called by the container's component.
+ * To set the size programmatically from within the container please
+ * use the public setSize method
+ *
+ * @param {[Int]} width in px
+ * @param {[Int]} height in px
+ *
+ * @returns {void}
+ */
+ _$setSize: function (width, height) {
+ if (width !== this.width || height !== this.height) {
+ this.width = width;
+ this.height = height;
+ var cl = this._contentElement[0];
+ var hdelta = cl.offsetWidth - cl.clientWidth;
+ var vdelta = cl.offsetHeight - cl.clientHeight;
+ this._contentElement.width(this.width - hdelta)
+ .height(this.height - vdelta);
+ this.emit('resize');
+ }
+ }
+ });
+
+ /**
+ * Pops a content item out into a new browser window.
+ * This is achieved by
+ *
+ * - Creating a new configuration with the content item as root element
+ * - Serializing and minifying the configuration
+ * - Opening the current window's URL with the configuration as a GET parameter
+ * - GoldenLayout when opened in the new window will look for the GET parameter
+ * and use it instead of the provided configuration
+ *
+ * @param {Object} config GoldenLayout item config
+ * @param {Object} dimensions A map with width, height, top and left
+ * @param {String} parentId The id of the element the item will be appended to on popIn
+ * @param {Number} indexInParent The position of this element within its parent
+ * @param {lm.LayoutManager} layoutManager
+ */
+ lm.controls.BrowserPopout = function (config, dimensions, parentId, indexInParent, layoutManager) {
+ lm.utils.EventEmitter.call(this);
+ this.isInitialised = false;
+
+ this._config = config;
+ this._dimensions = dimensions;
+ this._parentId = parentId;
+ this._indexInParent = indexInParent;
+ this._layoutManager = layoutManager;
+ this._popoutWindow = null;
+ this._id = null;
+ this._createWindow();
+ };
+
+ lm.utils.copy(lm.controls.BrowserPopout.prototype, {
+
+ toConfig: function () {
+ if (this.isInitialised === false) {
+ throw new Error('Can\'t create config, layout not yet initialised');
+ return;
+ }
+ return {
+ dimensions: {
+ width: this.getGlInstance().width,
+ height: this.getGlInstance().height,
+ left: this._popoutWindow.screenX || this._popoutWindow.screenLeft,
+ top: this._popoutWindow.screenY || this._popoutWindow.screenTop
+ },
+ content: this.getGlInstance().toConfig().content,
+ parentId: this._parentId,
+ indexInParent: this._indexInParent
+ };
+ },
+
+ getGlInstance: function () {
+ return this._popoutWindow.__glInstance;
+ },
+
+ getWindow: function () {
+ return this._popoutWindow;
+ },
+
+ close: function () {
+ if (this.getGlInstance()) {
+ this.getGlInstance()._$closeWindow();
+ } else {
+ try {
+ this.getWindow().close();
+ } catch (e) {
+ }
+ }
+ },
+
+ /**
+ * Returns the popped out item to its original position. If the original
+ * parent isn't available anymore it falls back to the layout's topmost element
+ */
+ popIn: function () {
+ var childConfig,
+ parentItem,
+ index = this._indexInParent;
+
+ if (this._parentId) {
+
+ /*
+ * The $.extend call seems a bit pointless, but it's crucial to
+ * copy the config returned by this.getGlInstance().toConfig()
+ * onto a new object. Internet Explorer keeps the references
+ * to objects on the child window, resulting in the following error
+ * once the child window is closed:
+ *
+ * The callee (server [not server application]) is not available and disappeared
+ */
+ childConfig = $.extend(true, {}, this.getGlInstance().toConfig()).content[0];
+ parentItem = this._layoutManager.root.getItemsById(this._parentId)[0];
+
+ /*
+ * Fallback if parentItem is not available. Either add it to the topmost
+ * item or make it the topmost item if the layout is empty
+ */
+ if (!parentItem) {
+ if (this._layoutManager.root.contentItems.length > 0) {
+ parentItem = this._layoutManager.root.contentItems[0];
+ } else {
+ parentItem = this._layoutManager.root;
+ }
+ index = 0;
+ }
+ }
+
+ parentItem.addChild(childConfig, this._indexInParent);
+ this.close();
+ },
+
+ /**
+ * Creates the URL and window parameter
+ * and opens a new window
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _createWindow: function () {
+ var checkReadyInterval,
+ url = this._createUrl(),
+
+ /**
+ * Bogus title to prevent re-usage of existing window with the
+ * same title. The actual title will be set by the new window's
+ * GoldenLayout instance if it detects that it is in subWindowMode
+ */
+ title = Math.floor(Math.random() * 1000000).toString(36),
+
+ /**
+ * The options as used in the window.open string
+ */
+ options = this._serializeWindowOptions({
+ width: this._dimensions.width,
+ height: this._dimensions.height,
+ innerWidth: this._dimensions.width,
+ innerHeight: this._dimensions.height,
+ menubar: 'no',
+ toolbar: 'no',
+ location: 'no',
+ personalbar: 'no',
+ resizable: 'yes',
+ scrollbars: 'no',
+ status: 'no'
+ });
+
+ this._popoutWindow = window.open(url, title, options);
+
+ if (!this._popoutWindow) {
+ if (this._layoutManager.config.settings.blockedPopoutsThrowError === true) {
+ var error = new Error('Popout blocked');
+ error.type = 'popoutBlocked';
+ throw error;
+ } else {
+ return;
+ }
+ }
+
+ $(this._popoutWindow)
+ .on('load', lm.utils.fnBind(this._positionWindow, this))
+ .on('unload beforeunload', lm.utils.fnBind(this._onClose, this));
+
+ /**
+ * Polling the childwindow to find out if GoldenLayout has been initialised
+ * doesn't seem optimal, but the alternatives - adding a callback to the parent
+ * window or raising an event on the window object - both would introduce knowledge
+ * about the parent to the child window which we'd rather avoid
+ */
+ checkReadyInterval = setInterval(lm.utils.fnBind(function () {
+ if (this._popoutWindow.__glInstance && this._popoutWindow.__glInstance.isInitialised) {
+ this._onInitialised();
+ clearInterval(checkReadyInterval);
+ }
+ }, this), 10);
+ },
+
+ /**
+ * Serialises a map of key:values to a window options string
+ *
+ * @param {Object} windowOptions
+ *
+ * @returns {String} serialised window options
+ */
+ _serializeWindowOptions: function (windowOptions) {
+ var windowOptionsString = [], key;
+
+ for (key in windowOptions) {
+ windowOptionsString.push(key + '=' + windowOptions[key]);
+ }
+
+ return windowOptionsString.join(',');
+ },
+
+ /**
+ * Creates the URL for the new window, including the
+ * config GET parameter
+ *
+ * @returns {String} URL
+ */
+ _createUrl: function () {
+ var config = { content: this._config },
+ storageKey = 'gl-window-config-' + lm.utils.getUniqueId(),
+ urlParts;
+
+ config = (new lm.utils.ConfigMinifier()).minifyConfig(config);
+
+ try {
+ localStorage.setItem(storageKey, JSON.stringify(config));
+ } catch (e) {
+ throw new Error('Error while writing to localStorage ' + e.toString());
+ }
+
+ urlParts = document.location.href.split('?');
+
+ // URL doesn't contain GET-parameters
+ if (urlParts.length === 1) {
+ return urlParts[0] + '?gl-window=' + storageKey;
+
+ // URL contains GET-parameters
+ } else {
+ return document.location.href + '&gl-window=' + storageKey;
+ }
+ },
+
+ /**
+ * Move the newly created window roughly to
+ * where the component used to be.
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _positionWindow: function () {
+ this._popoutWindow.moveTo(this._dimensions.left, this._dimensions.top);
+ this._popoutWindow.focus();
+ },
+
+ /**
+ * Callback when the new window is opened and the GoldenLayout instance
+ * within it is initialised
+ *
+ * @returns {void}
+ */
+ _onInitialised: function () {
+ this.isInitialised = true;
+ this.getGlInstance().on('popIn', this.popIn, this);
+ this.emit('initialised');
+ },
+
+ /**
+ * Invoked 50ms after the window unload event
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _onClose: function () {
+ setTimeout(lm.utils.fnBind(this.emit, this, ['closed']), 50);
+ }
+ });
+ /**
+ * This class creates a temporary container
+ * for the component whilst it is being dragged
+ * and handles drag events
+ *
+ * @constructor
+ * @private
+ *
+ * @param {Number} x The initial x position
+ * @param {Number} y The initial y position
+ * @param {lm.utils.DragListener} dragListener
+ * @param {lm.LayoutManager} layoutManager
+ * @param {lm.item.AbstractContentItem} contentItem
+ * @param {lm.item.AbstractContentItem} originalParent
+ */
+ lm.controls.DragProxy = function (x, y, dragListener, layoutManager, contentItem, originalParent) {
+
+ lm.utils.EventEmitter.call(this);
+
+ this._dragListener = dragListener;
+ this._layoutManager = layoutManager;
+ this._contentItem = contentItem;
+ this._originalParent = originalParent;
+
+ this._area = null;
+ this._lastValidArea = null;
+
+ this._dragListener.on('drag', this._onDrag, this);
+ this._dragListener.on('dragStop', this._onDrop, this);
+
+ this.element = $(lm.controls.DragProxy._template);
+ if (originalParent && originalParent._side) {
+ this._sided = originalParent._sided;
+ this.element.addClass('lm_' + originalParent._side);
+ if (['right', 'bottom'].indexOf(originalParent._side) >= 0)
+ this.element.find('.lm_content').after(this.element.find('.lm_header'));
+ }
+ this.element.css({ left: x, top: y });
+ this.element.find('.lm_tab').attr('title', lm.utils.stripTags(this._contentItem.config.title));
+ this.element.find('.lm_title').html(this._contentItem.config.title);
+ this.childElementContainer = this.element.find('.lm_content');
+ this.childElementContainer.append(contentItem.element);
+
+ this._updateTree();
+ this._layoutManager._$calculateItemAreas();
+ this._setDimensions();
+
+ $(document.body).append(this.element);
+
+ var offset = this._layoutManager.container.offset();
+
+ this._minX = offset.left;
+ this._minY = offset.top;
+ this._maxX = this._layoutManager.container.width() + this._minX;
+ this._maxY = this._layoutManager.container.height() + this._minY;
+ this._width = this.element.width();
+ this._height = this.element.height();
+
+ this._setDropPosition(x, y);
+ };
+
+ lm.controls.DragProxy._template = '<div class="lm_dragProxy">' +
+ '<div class="lm_header">' +
+ '<ul class="lm_tabs">' +
+ '<li class="lm_tab lm_active"><i class="lm_left"></i>' +
+ '<span class="lm_title"></span>' +
+ '<i class="lm_right"></i></li>' +
+ '</ul>' +
+ '</div>' +
+ '<div class="lm_content"></div>' +
+ '</div>';
+
+ lm.utils.copy(lm.controls.DragProxy.prototype, {
+
+ /**
+ * Callback on every mouseMove event during a drag. Determines if the drag is
+ * still within the valid drag area and calls the layoutManager to highlight the
+ * current drop area
+ *
+ * @param {Number} offsetX The difference from the original x position in px
+ * @param {Number} offsetY The difference from the original y position in px
+ * @param {jQuery DOM event} event
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _onDrag: function (offsetX, offsetY, event) {
+
+ event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event;
+
+ var x = event.pageX,
+ y = event.pageY,
+ isWithinContainer = x > this._minX && x < this._maxX && y > this._minY && y < this._maxY;
+
+ if (!isWithinContainer && this._layoutManager.config.settings.constrainDragToContainer === true) {
+ return;
+ }
+
+ this._setDropPosition(x, y);
+ },
+
+ /**
+ * Sets the target position, highlighting the appropriate area
+ *
+ * @param {Number} x The x position in px
+ * @param {Number} y The y position in px
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _setDropPosition: function (x, y) {
+ this.element.css({ left: x, top: y });
+ this._area = this._layoutManager._$getArea(x, y);
+
+ if (this._area !== null) {
+ this._lastValidArea = this._area;
+ this._area.contentItem._$highlightDropZone(x, y, this._area);
+ }
+ },
+
+ /**
+ * Callback when the drag has finished. Determines the drop area
+ * and adds the child to it
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _onDrop: function () {
+ this._layoutManager.dropTargetIndicator.hide();
+
+ /*
+ * Valid drop area found
+ */
+ if (this._area !== null) {
+ this._area.contentItem._$onDrop(this._contentItem, this._area);
+
+ /**
+ * No valid drop area available at present, but one has been found before.
+ * Use it
+ */
+ } else if (this._lastValidArea !== null) {
+ this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea);
+
+ /**
+ * No valid drop area found during the duration of the drag. Return
+ * content item to its original position if a original parent is provided.
+ * (Which is not the case if the drag had been initiated by createDragSource)
+ */
+ } else if (this._originalParent) {
+ this._originalParent.addChild(this._contentItem);
+
+ /**
+ * The drag didn't ultimately end up with adding the content item to
+ * any container. In order to ensure clean up happens, destroy the
+ * content item.
+ */
+ } else {
+ this._contentItem._$destroy();
+ }
+
+ this.element.remove();
+
+ this._layoutManager.emit('itemDropped', this._contentItem);
+ },
+
+ /**
+ * Removes the item from its original position within the tree
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _updateTree: function () {
+
+ /**
+ * parent is null if the drag had been initiated by a external drag source
+ */
+ if (this._contentItem.parent) {
+ this._contentItem.parent.removeChild(this._contentItem, true);
+ }
+
+ this._contentItem._$setParent(this);
+ },
+
+ /**
+ * Updates the Drag Proxie's dimensions
+ *
+ * @private
+ *
+ * @returns {void}
+ */
+ _setDimensions: function () {
+ var dimensions = this._layoutManager.config.dimensions,
+ width = dimensions.dragProxyWidth,
+ height = dimensions.dragProxyHeight;
+
+ this.element.width(width);
+ this.element.height(height);
+ width -= (this._sided ? dimensions.headerHeight : 0);
+ height -= (!this._sided ? dimensions.headerHeight : 0);
+ this.childElementContainer.width(width);
+ this.childElementContainer.height(height);
+ this._contentItem.element.width(width);
+ this._contentItem.element.height(height);
+ this._contentItem.callDownwards('_$show');
+ this._contentItem.callDownwards('setSize');
+ }
+ });
+
+ /**
+ * Allows for any DOM item to create a component on drag
+ * start tobe dragged into the Layout
+ *
+ * @param {jQuery element} element
+ * @param {Object} itemConfig the configuration for the contentItem that will be created
+ * @param {LayoutManager} layoutManager
+ *
+ * @constructor
+ */
+ lm.controls.DragSource = function (element, itemConfig, layoutManager) {
+ this._element = element;
+ this._itemConfig = itemConfig;
+ this._layoutManager = layoutManager;
+ this._dragListener = null;
+
+ this._createDragListener();
+ };
+
+ lm.utils.copy(lm.controls.DragSource.prototype, {
+
+ /**
+ * Called initially and after every drag
+ *
+ * @returns {void}
+ */
+ _createDragListener: function () {
+ if (this._dragListener !== null) {
+ this._dragListener.destroy();
+ }
+
+ this._dragListener = new lm.utils.DragListener(this._element);
+ this._dragListener.on('dragStart', this._onDragStart, this);
+ this._dragListener.on('dragStop', this._createDragListener, this);
+ },
+
+ /**
+ * Callback for the DragListener's dragStart event
+ *
+ * @param {int} x the x position of the mouse on dragStart
+ * @param {int} y the x position of the mouse on dragStart
+ *
+ * @returns {void}
+ */
+ _onDragStart: function (x, y) {
+ var itemConfig = this._itemConfig;
+ if (lm.utils.isFunction(itemConfig)) {
+ itemConfig = itemConfig();
+ }
+ var contentItem = this._layoutManager._$normalizeContentItem($.extend(true, {}, itemConfig)),
+ dragProxy = new lm.controls.DragProxy(x, y, this._dragListener, this._layoutManager, contentItem, null);
+
+ this._layoutManager.transitionIndicator.transitionElements(this._element, dragProxy.element);
+ }
+ });
+
+ lm.controls.DropTargetIndicator = function () {
+ this.element = $(lm.controls.DropTargetIndicator._template);
+ $(document.body).append(this.element);
+ };
+
+ lm.controls.DropTargetIndicator._template = '<div class="lm_dropTargetIndicator"><div class="lm_inner"></div></div>';
+
+ lm.utils.copy(lm.controls.DropTargetIndicator.prototype, {
+ destroy: function () {
+ this.element.remove();
+ },
+
+ highlight: function (x1, y1, x2, y2) {
+ this.highlightArea({ x1: x1, y1: y1, x2: x2, y2: y2 });
+ },
+
+ highlightArea: function (area) {
+ this.element.css({
+ left: area.x1,
+ top: area.y1,
+ width: area.x2 - area.x1,
+ height: area.y2 - area.y1
+ }).show();
+ },
+
+ hide: function () {
+ this.element.hide();
+ }
+ });
+ /**
+ * This class represents a header above a Stack ContentItem.
+ *
+ * @param {lm.LayoutManager} layoutManager
+ * @param {lm.item.AbstractContentItem} parent
+ */
+ lm.controls.Header = function (layoutManager, parent) {
+ lm.utils.EventEmitter.call(this);
+
+ this.layoutManager = layoutManager;
+ this.element = $(lm.controls.Header._template);
+
+ if (this.layoutManager.config.settings.selectionEnabled === true) {
+ this.element.addClass('lm_selectable');
+ this.element.on('click touchstart', lm.utils.fnBind(this._onHeaderClick, this));
+ }
+
+ this.tabsContainer = this.element.find('.lm_tabs');
+ this.tabDropdownContainer = this.element.find('.lm_tabdropdown_list');
+ this.tabDropdownContainer.hide();
+ this.controlsContainer = this.element.find('.lm_controls');
+ this.parent = parent;
+ this.parent.on('resize', this._updateTabSizes, this);
+ this.tabs = [];
+ this.activeContentItem = null;
+ this.closeButton = null;
+ this.tabDropdownButton = null;
+ this.hideAdditionalTabsDropdown = lm.utils.fnBind(this._hideAdditionalTabsDropdown, this);
+ $(document).mouseup(this.hideAdditionalTabsDropdown);
+
+ this._lastVisibleTabIndex = -1;
+ this._tabControlOffset = this.layoutManager.config.settings.tabControlOffset;
+ this._createControls();
+ };
+
+ lm.controls.Header._template = [
+ '<div class="lm_header">',
+ '<ul class="lm_tabs"></ul>',
+ '<ul class="lm_controls"></ul>',
+ '<ul class="lm_tabdropdown_list"></ul>',
+ '</div>'
+ ].join('');
+
+ lm.utils.copy(lm.controls.Header.prototype, {
+
+ /**
+ * Creates a new tab and associates it with a contentItem
+ *
+ * @param {lm.item.AbstractContentItem} contentItem
+ * @param {Integer} index The position of the tab
+ *
+ * @returns {void}
+ */
+ createTab: function (contentItem, index) {
+ var tab, i;
+
+ //If there's already a tab relating to the
+ //content item, don't do anything
+ for (i = 0; i < this.tabs.length; i++) {
+ if (this.tabs[i].contentItem === contentItem) {
+ return;
+ }
+ }
+
+ tab = new lm.controls.Tab(this, contentItem);
+
+ if (this.tabs.length === 0) {
+ this.tabs.push(tab);
+ this.tabsContainer.append(tab.element);
+ return;
+ }
+
+ if (index === undefined) {
+ index = this.tabs.length;
+ }
+
+ if (index > 0) {
+ this.tabs[index - 1].element.after(tab.element);
+ } else {
+ this.tabs[0].element.before(tab.element);
+ }
+
+ this.tabs.splice(index, 0, tab);
+ this._updateTabSizes();
+ },
+
+ /**
+ * Finds a tab based on the contentItem its associated with and removes it.
+ *
+ * @param {lm.item.AbstractContentItem} contentItem
+ *
+ * @returns {void}
+ */
+ removeTab: function (contentItem) {
+ for (var i = 0; i < this.tabs.length; i++) {
+ if (this.tabs[i].contentItem === contentItem) {
+ this.tabs[i]._$destroy();
+ this.tabs.splice(i, 1);
+ return;
+ }
+ }
+
+ throw new Error('contentItem is not controlled by this header');
+ },
+
+ /**
+ * The programmatical equivalent of clicking a Tab.
+ *
+ * @param {lm.item.AbstractContentItem} contentItem
+ */
+ setActiveContentItem: function (contentItem) {
+ var i, j, isActive, activeTab;
+
+ for (i = 0; i < this.tabs.length; i++) {
+ isActive = this.tabs[i].contentItem === contentItem;
+ this.tabs[i].setActive(isActive);
+ if (isActive === true) {
+ this.activeContentItem = contentItem;
+ this.parent.config.activeItemIndex = i;
+ }
+ }
+
+ if (this.layoutManager.config.settings.reorderOnTabMenuClick) {
+ /**
+ * If the tab selected was in the dropdown, move everything down one to make way for this one to be the first.
+ * This will make sure the most used tabs stay visible.
+ */
+ if (this._lastVisibleTabIndex !== -1 && this.parent.config.activeItemIndex > this._lastVisibleTabIndex) {
+ activeTab = this.tabs[this.parent.config.activeItemIndex];
+ for (j = this.parent.config.activeItemIndex; j > 0; j--) {
+ this.tabs[j] = this.tabs[j - 1];
+ }
+ this.tabs[0] = activeTab;
+ this.parent.config.activeItemIndex = 0;
+ }
+ }
+
+ this._updateTabSizes();
+ this.parent.emitBubblingEvent('stateChanged');
+ },
+
+ /**
+ * Programmatically operate with header position.
+ *
+ * @param {string} position one of ('top','left','right','bottom') to set or empty to get it.
+ *
+ * @returns {string} previous header position
+ */
+ position: function (position) {
+ var previous = this.parent._header.show;
+ if (previous && !this.parent._side)
+ previous = 'top';
+ if (position !== undefined && this.parent._header.show != position) {
+ this.parent._header.show = position;
+ this.parent._setupHeaderPosition();
+ }
+ return previous;
+ },
+
+ /**
+ * Programmatically set closability.
+ *
+ * @package private
+ * @param {Boolean} isClosable Whether to enable/disable closability.
+ *
+ * @returns {Boolean} Whether the action was successful
+ */
+ _$setClosable: function (isClosable) {
+ if (this.closeButton && this._isClosable()) {
+ this.closeButton.element[isClosable ? "show" : "hide"]();
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Destroys the entire header
+ *
+ * @package private
+ *
+ * @returns {void}
+ */
+ _$destroy: function () {
+ this.emit('destroy', this);
+
+ for (var i = 0; i < this.tabs.length; i++) {
+ this.tabs[i]._$destroy();
+ }
+ $(document).off('mouseup', this.hideAdditionalTabsDropdown);
+ this.element.remove();
+ },
+
+ /**
+ * get settings from header
+ *
+ * @returns {string} when exists
+ */
+ _getHeaderSetting: function (name) {
+ if (name in this.parent._header)
+ return this.parent._header[name];
+ },
+ /**
+ * Creates the popout, maximise and close buttons in the header's top right corner
+ *
+ * @returns {void}
+ */
+ _createControls: function () {
+ var closeStack,
+ popout,
+ label,
+ maximiseLabel,
+ minimiseLabel,
+ maximise,
+ maximiseButton,
+ tabDropdownLabel,
+ showTabDropdown;
+
+ /**
+ * Dropdown to show additional tabs.
+ */
+ showTabDropdown = lm.utils.fnBind(this._showAdditionalTabsDropdown, this);
+ tabDropdownLabel = this.layoutManager.config.labels.tabDropdown;
+ this.tabDropdownButton = new lm.controls.HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', showTabDropdown);
+ this.tabDropdownButton.element.hide();
+
+ /**
+ * Popout control to launch component in new window.
+ */
+ if (this._getHeaderSetting('popout')) {
+ popout = lm.utils.fnBind(this._onPopoutClick, this);
+ label = this._getHeaderSetting('popout');
+ new lm.controls.HeaderButton(this, label, 'lm_popout', popout);
+ }
+
+ /**
+ * Maximise control - set the component to the full size of the layout
+ */
+ if (this._getHeaderSetting('maximise')) {
+ maximise = lm.utils.fnBind(this.parent.toggleMaximise, this.parent);
+ maximiseLabel = this._getHeaderSetting('maximise');
+ minimiseLabel = this._getHeaderSetting('minimise');
+ maximiseButton = new lm.controls.HeaderButton(this, maximiseLabel, 'lm_maximise', maximise);
+
+ this.parent.on('maximised', function () {
+ maximiseButton.element.attr('title', minimiseLabel);
+ });
+
+ this.parent.on('minimised', function () {
+ maximiseButton.element.attr('title', maximiseLabel);
+ });
+ }
+
+ /**
+ * Close button
+ */
+ if (this._isClosable()) {
+ closeStack = lm.utils.fnBind(this.parent.remove, this.parent);
+ label = this._getHeaderSetting('close');
+ this.closeButton = new lm.controls.HeaderButton(this, label, 'lm_close', closeStack);
+ }
+ },
+
+ /**
+ * Shows drop down for additional tabs when there are too many to display.
+ *
+ * @returns {void}
+ */
+ _showAdditionalTabsDropdown: function () {
+ this.tabDropdownContainer.show();
+ },
+
+ /**
+ * Hides drop down for additional tabs when there are too many to display.
+ *
+ * @returns {void}
+ */
+ _hideAdditionalTabsDropdown: function (e) {
+ this.tabDropdownContainer.hide();
+ },
+
+ /**
+ * Checks whether the header is closable based on the parent config and
+ * the global config.
+ *
+ * @returns {Boolean} Whether the header is closable.
+ */
+ _isClosable: function () {
+ return this.parent.config.isClosable && this.layoutManager.config.settings.showCloseIcon;
+ },
+
+ _onPopoutClick: function () {
+ if (this.layoutManager.config.settings.popoutWholeStack === true) {
+ this.parent.popout();
+ } else {
+ this.activeContentItem.popout();
+ }
+ },
+
+
+ /**
+ * Invoked when the header's background is clicked (not it's tabs or controls)
+ *
+ * @param {jQuery DOM event} event
+ *
+ * @returns {void}
+ */
+ _onHeaderClick: function (event) {
+ if (event.target === this.element[0]) {
+ this.parent.select();
+ }
+ },
+
+ /**
+ * Pushes the tabs to the tab dropdown if the available space is not sufficient
+ *
+ * @returns {void}
+ */
+ _updateTabSizes: function (showTabMenu) {
+ if (this.tabs.length === 0) {
+ return;
+ }
+
+ //Show the menu based on function argument
+ this.tabDropdownButton.element.toggle(showTabMenu === true);
+
+ var size = function (val) {
+ return val ? 'width' : 'height';
+ };
+ this.element.css(size(!this.parent._sided), '');
+ this.element[size(this.parent._sided)](this.layoutManager.config.dimensions.headerHeight);
+ var availableWidth = this.element.outerWidth() - this.controlsContainer.outerWidth() - this._tabControlOffset,
+ cumulativeTabWidth = 0,
+ visibleTabWidth = 0,
+ tabElement,
+ i,
+ j,
+ marginLeft,
+ overlap = 0,
+ tabWidth,
+ tabOverlapAllowance = this.layoutManager.config.settings.tabOverlapAllowance,
+ tabOverlapAllowanceExceeded = false,
+ activeIndex = (this.activeContentItem ? this.tabs.indexOf(this.activeContentItem.tab) : 0),
+ activeTab = this.tabs[activeIndex];
+ if (this.parent._sided)
+ availableWidth = this.element.outerHeight() - this.controlsContainer.outerHeight() - this._tabControlOffset;
+ this._lastVisibleTabIndex = -1;
+
+ for (i = 0; i < this.tabs.length; i++) {
+ tabElement = this.tabs[i].element;
+
+ //Put the tab in the tabContainer so its true width can be checked
+ this.tabsContainer.append(tabElement);
+ tabWidth = tabElement.outerWidth() + parseInt(tabElement.css('margin-right'), 10);
+
+ cumulativeTabWidth += tabWidth;
+
+ //Include the active tab's width if it isn't already
+ //This is to ensure there is room to show the active tab
+ if (activeIndex <= i) {
+ visibleTabWidth = cumulativeTabWidth;
+ } else {
+ visibleTabWidth = cumulativeTabWidth + activeTab.element.outerWidth() + parseInt(activeTab.element.css('margin-right'), 10);
+ }
+
+ // If the tabs won't fit, check the overlap allowance.
+ if (visibleTabWidth > availableWidth) {
+
+ //Once allowance is exceeded, all remaining tabs go to menu.
+ if (!tabOverlapAllowanceExceeded) {
+
+ //No overlap for first tab or active tab
+ //Overlap spreads among non-active, non-first tabs
+ if (activeIndex > 0 && activeIndex <= i) {
+ overlap = (visibleTabWidth - availableWidth) / (i - 1);
+ } else {
+ overlap = (visibleTabWidth - availableWidth) / i;
+ }
+
+ //Check overlap against allowance.
+ if (overlap < tabOverlapAllowance) {
+ for (j = 0; j <= i; j++) {
+ marginLeft = (j !== activeIndex && j !== 0) ? '-' + overlap + 'px' : '';
+ this.tabs[j].element.css({ 'z-index': i - j, 'margin-left': marginLeft });
+ }
+ this._lastVisibleTabIndex = i;
+ this.tabsContainer.append(tabElement);
+ } else {
+ tabOverlapAllowanceExceeded = true;
+ }
+
+ } else if (i === activeIndex) {
+ //Active tab should show even if allowance exceeded. (We left room.)
+ tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+ this.tabsContainer.append(tabElement);
+ }
+
+ if (tabOverlapAllowanceExceeded && i !== activeIndex) {
+ if (showTabMenu) {
+ //Tab menu already shown, so we just add to it.
+ tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+ this.tabDropdownContainer.append(tabElement);
+ } else {
+ //We now know the tab menu must be shown, so we have to recalculate everything.
+ this._updateTabSizes(true);
+ return;
+ }
+ }
+
+ }
+ else {
+ this._lastVisibleTabIndex = i;
+ tabElement.css({ 'z-index': 'auto', 'margin-left': '' });
+ this.tabsContainer.append(tabElement);
+ }
+ }
+
+ }
+ });
+
+
+ lm.controls.HeaderButton = function (header, label, cssClass, action) {
+ this._header = header;
+ this.element = $('<li class="' + cssClass + '" title="' + label + '"></li>');
+ this._header.on('destroy', this._$destroy, this);
+ this._action = action;
+ this.element.on('click touchstart', this._action);
+ this._header.controlsContainer.append(this.element);
+ };
+
+ lm.utils.copy(lm.controls.HeaderButton.prototype, {
+ _$destroy: function () {
+ this.element.off();
+ this.element.remove();
+ }
+ });
+ lm.controls.Splitter = function (isVertical, size, grabSize) {
+ this._isVertical = isVertical;
+ this._size = size;
+ this._grabSize = grabSize < size ? size : grabSize;
+
+ this.element = this._createElement();
+ this._dragListener = new lm.utils.DragListener(this.element);
+ };
+
+ lm.utils.copy(lm.controls.Splitter.prototype, {
+ on: function (event, callback, context) {
+ this._dragListener.on(event, callback, context);
+ },
+
+ _$destroy: function () {
+ this.element.remove();
+ },
+
+ _createElement: function () {
+ var dragHandle = $('<div class="lm_drag_handle"></div>');
+ var element = $('<div class="lm_splitter"></div>');
+ element.append(dragHandle);
+
+ var handleExcessSize = this._grabSize - this._size;
+ var handleExcessPos = handleExcessSize / 2;
+
+ if (this._isVertical) {
+ dragHandle.css('top', -handleExcessPos);
+ dragHandle.css('height', this._size + handleExcessSize);
+ element.addClass('lm_vertical');
+ element['height'](this._size);
+ } else {
+ dragHandle.css('left', -handleExcessPos);
+ dragHandle.css('width', this._size + handleExcessSize);
+ element.addClass('lm_horizontal');
+ element['width'](this._size);
+ }
+
+ return element;
+ }
+ });
+
+ /**
+ * Represents an individual tab within a Stack's header
+ *
+ * @param {lm.controls.Header} header
+ * @param {lm.items.AbstractContentItem} contentItem
+ *
+ * @constructor
+ */
+ lm.controls.Tab = function (header, contentItem) {
+ this.header = header;
+ this.contentItem = contentItem;
+ this.element = $(lm.controls.Tab._template);
+ this.titleElement = this.element.find('.lm_title');
+ this.closeElement = this.element.find('.lm_close_tab');
+ this.closeElement[contentItem.config.isClosable ? 'show' : 'hide']();
+ this.isActive = false;
+
+ this.setTitle(contentItem.config.title);
+ this.contentItem.on('titleChanged', this.setTitle, this);
+
+ this._layoutManager = this.contentItem.layoutManager;
+
+ if (
+ this._layoutManager.config.settings.reorderEnabled === true &&
+ contentItem.config.reorderEnabled === true
+ ) {
+ this._dragListener = new lm.utils.DragListener(this.element);
+ this._dragListener.on('dragStart', this._onDragStart, this);
+ this.contentItem.on('destroy', this._dragListener.destroy, this._dragListener);
+ }
+
+ this._onTabClickFn = lm.utils.fnBind(this._onTabClick, this);
+ this._onCloseClickFn = lm.utils.fnBind(this._onCloseClick, this);
+
+ this.element.on('mousedown touchstart', this._onTabClickFn);
+
+ if (this.contentItem.config.isClosable) {
+ this.closeElement.on('click touchstart', this._onCloseClickFn);
+ this.closeElement.on('mousedown', this._onCloseMousedown);
+ } else {
+ this.closeElement.remove();
+ }
+
+ this.contentItem.tab = this;
+ this.contentItem.emit('tab', this);
+ this.contentItem.layoutManager.emit('tabCreated', this);
+
+ if (this.contentItem.isComponent) {
+ this.contentItem.container.tab = this;
+ this.contentItem.container.emit('tab', this);
+ }
+ };
+
+ /**
+ * The tab's html template
+ *
+ * @type {String}
+ */
+ lm.controls.Tab._template = '<li class="lm_tab"><i class="lm_left"></i>' +
+ '<span class="lm_title"></span><div class="lm_close_tab"></div>' +
+ '<i class="lm_right"></i></li>';
+
+ lm.utils.copy(lm.controls.Tab.prototype, {
+
+ /**
+ * Sets the tab's title to the provided string and sets
+ * its title attribute to a pure text representation (without
+ * html tags) of the same string.
+ *
+ * @public
+ * @param {String} title can contain html
+ */
+ setTitle: function (title) {
+ this.element.attr('title', lm.utils.stripTags(title));
+ this.titleElement.html(title);
+ },
+
+ /**
+ * Sets this tab's active state. To programmatically
+ * switch tabs, use header.setActiveContentItem( item ) instead.
+ *
+ * @public
+ * @param {Boolean} isActive
+ */
+ setActive: function (isActive) {
+ if (isActive === this.isActive) {
+ return;
+ }
+ this.isActive = isActive;
+
+ if (isActive) {
+ this.element.addClass('lm_active');
+ } else {
+ this.element.removeClass('lm_active');
+ }
+ },
+
+ /**
+ * Destroys the tab
+ *
+ * @private
+ * @returns {void}
+ */
+ _$destroy: function () {
+ this._layoutManager.emit('tabDestroyed', this);
+ this.element.off('mousedown touchstart', this._onTabClickFn);
+ this.closeElement.off('click touchstart', this._onCloseClickFn);
+ if (this._dragListener) {
+ this.contentItem.off('destroy', this._dragListener.destroy, this._dragListener);
+ this._dragListener.off('dragStart', this._onDragStart);
+ this._dragListener = null;
+ }
+ this.element.remove();
+ },
+
+ /**
+ * Callback for the DragListener
+ *
+ * @param {Number} x The tabs absolute x position
+ * @param {Number} y The tabs absolute y position
+ *
+ * @private
+ * @returns {void}
+ */
+ _onDragStart: function (x, y) {
+ if (this.contentItem.parent.isMaximised === true) {
+ this.contentItem.parent.toggleMaximise();
+ }
+ new lm.controls.DragProxy(
+ x,
+ y,
+ this._dragListener,
+ this._layoutManager,
+ this.contentItem,
+ this.header.parent
+ );
+ },
+
+ /**
+ * Callback when the tab is clicked
+ *
+ * @param {jQuery DOM event} event
+ *
+ * @private
+ * @returns {void}
+ */
+ _onTabClick: function (event) {
+ // left mouse button or tap
+ if (event.button === 0 || event.type === 'touchstart') {
+ var activeContentItem = this.header.parent.getActiveContentItem();
+ if (this.contentItem !== activeContentItem) {
+ this.header.parent.setActiveContentItem(this.contentItem);
+ }
+
+ // middle mouse button
+ } else if (event.button === 1 && this.contentItem.config.isClosable) {
+ this._onCloseClick(event);
+ }
+ },
+
+ /**
+ * Callback when the tab's close button is
+ * clicked
+ *
+ * @param {jQuery DOM event} event
+ *
+ * @private
+ * @returns {void}
+ */
+ _onCloseClick: function (event) {
+ event.stopPropagation();
+ this.header.parent.removeChild(this.contentItem);
+ },
+
+
+ /**
+ * Callback to capture tab close button mousedown
+ * to prevent tab from activating.
+ *
+ * @param (jQuery DOM event) event
+ *
+ * @private
+ * @returns {void}
+ */
+ _onCloseMousedown: function (event) {
+ event.stopPropagation();
+ }
+ });
+
+ lm.controls.TransitionIndicator = function () {
+ this._element = $('<div class="lm_transition_indicator"></div>');
+ $(document.body).append(this._element);
+
+ this._toElement = null;
+ this._fromDimensions = null;
+ this._totalAnimationDuration = 200;
+ this._animationStartTime = null;
+ };
+
+ lm.utils.copy(lm.controls.TransitionIndicator.prototype, {
+ destroy: function () {
+ this._element.remove();
+ },
+
+ transitionElements: function (fromElement, toElement) {
+ /**
+ * TODO - This is not quite as cool as expected. Review.
+ */
+ return;
+ this._toElement = toElement;
+ this._animationStartTime = lm.utils.now();
+ this._fromDimensions = this._measure(fromElement);
+ this._fromDimensions.opacity = 0.8;
+ this._element.show().css(this._fromDimensions);
+ lm.utils.animFrame(lm.utils.fnBind(this._nextAnimationFrame, this));
+ },
+
+ _nextAnimationFrame: function () {
+ var toDimensions = this._measure(this._toElement),
+ animationProgress = (lm.utils.now() - this._animationStartTime) / this._totalAnimationDuration,
+ currentFrameStyles = {},
+ cssProperty;
+
+ if (animationProgress >= 1) {
+ this._element.hide();
+ return;
+ }
+
+ toDimensions.opacity = 0;
+
+ for (cssProperty in this._fromDimensions) {
+ currentFrameStyles[cssProperty] = this._fromDimensions[cssProperty] +
+ (toDimensions[cssProperty] - this._fromDimensions[cssProperty]) *
+ animationProgress;
+ }
+
+ this._element.css(currentFrameStyles);
+ lm.utils.animFrame(lm.utils.fnBind(this._nextAnimationFrame, this));
+ },
+
+ _measure: function (element) {
+ var offset = element.offset();
+
+ return {
+ left: offset.left,
+ top: offset.top,
+ width: element.outerWidth(),
+ height: element.outerHeight()
+ };
+ }
+ });
+ lm.errors.ConfigurationError = function (message, node) {
+ Error.call(this);
+
+ this.name = 'Configuration Error';
+ this.message = message;
+ this.node = node;
+ };
+
+ lm.errors.ConfigurationError.prototype = new Error();
+
+ /**
+ * This is the baseclass that all content items inherit from.
+ * Most methods provide a subset of what the sub-classes do.
+ *
+ * It also provides a number of functions for tree traversal
+ *
+ * @param {lm.LayoutManager} layoutManager
+ * @param {item node configuration} config
+ * @param {lm.item} parent
+ *
+ * @event stateChanged
+ * @event beforeItemDestroyed
+ * @event itemDestroyed
+ * @event itemCreated
+ * @event componentCreated
+ * @event rowCreated
+ * @event columnCreated
+ * @event stackCreated
+ *
+ * @constructor
+ */
+ lm.items.AbstractContentItem = function (layoutManager, config, parent) {
+ lm.utils.EventEmitter.call(this);
+
+ this.config = this._extendItemNode(config);
+ this.type = config.type;
+ this.contentItems = [];
+ this.parent = parent;
+
+ this.isInitialised = false;
+ this.isMaximised = false;
+ this.isRoot = false;
+ this.isRow = false;
+ this.isColumn = false;
+ this.isStack = false;
+ this.isComponent = false;
+
+ this.layoutManager = layoutManager;
+ this._pendingEventPropagations = {};
+ this._throttledEvents = ['stateChanged'];
+
+ this.on(lm.utils.EventEmitter.ALL_EVENT, this._propagateEvent, this);
+
+ if (config.content) {
+ this._createContentItems(config);
+ }
+ };
+
+ lm.utils.copy(lm.items.AbstractContentItem.prototype, {
+
+ /**
+ * Set the size of the component and its children, called recursively
+ *
+ * @abstract
+ * @returns void
+ */
+ setSize: function () {
+ throw new Error('Abstract Method');
+ },
+
+ /**
+ * Calls a method recursively downwards on the tree
+ *
+ * @param {String} functionName the name of the function to be called
+ * @param {[Array]}functionArguments optional arguments that are passed to every function
+ * @param {[bool]} bottomUp Call methods from bottom to top, defaults to false
+ * @param {[bool]} skipSelf Don't invoke the method on the class that calls it, defaults to false
+ *
+ * @returns {void}
+ */
+ callDownwards: function (functionName, functionArguments, bottomUp, skipSelf) {
+ var i;
+
+ if (bottomUp !== true && skipSelf !== true) {
+ this[functionName].apply(this, functionArguments || []);
+ }
+ for (i = 0; i < this.contentItems.length; i++) {
+ this.contentItems[i].callDownwards(functionName, functionArguments, bottomUp);
+ }
+ if (bottomUp === true && skipSelf !== true) {
+ this[functionName].apply(this, functionArguments || []);
+ }
+ },
+
+ /**
+ * Removes a child node (and its children) from the tree
+ *
+ * @param {lm.items.ContentItem} contentItem
+ *
+ * @returns {void}
+ */
+ removeChild: function (contentItem, keepChild) {
+
+ /*
+ * Get the position of the item that's to be removed within all content items this node contains
+ */
+ var index = lm.utils.indexOf(contentItem, this.contentItems);
+
+ /*
+ * Make sure the content item to be removed is actually a child of this item
+ */
+ if (index === -1) {
+ throw new Error('Can\'t remove child item. Unknown content item');
+ }
+
+ /**
+ * Call ._$destroy on the content item. This also calls ._$destroy on all its children
+ */
+ if (keepChild !== true) {
+ this.contentItems[index]._$destroy();
+ }
+
+ /**
+ * Remove the content item from this nodes array of children
+ */
+ this.contentItems.splice(index, 1);
+
+ /**
+ * Remove the item from the configuration
+ */
+ this.config.content.splice(index, 1);
+
+ /**
+ * If this node still contains other content items, adjust their size
+ */
+ if (this.contentItems.length > 0) {
+ this.callDownwards('setSize');
+
+ /**
+ * If this was the last content item, remove this node as well
+ */
+ } else if (!(this instanceof lm.items.Root) && this.config.isClosable === true) {
+ this.parent.removeChild(this);
+ }
+ },
+
+ /**
+ * Sets up the tree structure for the newly added child
+ * The responsibility for the actual DOM manipulations lies
+ * with the concrete item
+ *
+ * @param {lm.items.AbstractContentItem} contentItem
+ * @param {[Int]} index If omitted item will be appended
+ */
+ addChild: function (contentItem, index) {
+ if (index === undefined) {
+ index = this.contentItems.length;
+ }
+
+ this.contentItems.splice(index, 0, contentItem);
+
+ if (this.config.content === undefined) {
+ this.config.content = [];
+ }
+
+ this.config.content.splice(index, 0, contentItem.config);
+ contentItem.parent = this;
+
+ if (contentItem.parent.isInitialised === true && contentItem.isInitialised === false) {
+ contentItem._$init();
+ }
+ },
+
+ /**
+ * Replaces oldChild with newChild. This used to use jQuery.replaceWith... which for
+ * some reason removes all event listeners, so isn't really an option.
+ *
+ * @param {lm.item.AbstractContentItem} oldChild
+ * @param {lm.item.AbstractContentItem} newChild
+ *
+ * @returns {void}
+ */
+ replaceChild: function (oldChild, newChild, _$destroyOldChild) {
+
+ newChild = this.layoutManager._$normalizeContentItem(newChild);
+
+ var index = lm.utils.indexOf(oldChild, this.contentItems),
+ parentNode = oldChild.element[0].parentNode;
+
+ if (index === -1) {
+ throw new Error('Can\'t replace child. oldChild is not child of this');
+ }
+
+ parentNode.replaceChild(newChild.element[0], oldChild.element[0]);
+
+ /*
+ * Optionally destroy the old content item
+ */
+ if (_$destroyOldChild === true) {
+ oldChild.parent = null;
+ oldChild._$destroy();
+ }
+
+ /*
+ * Wire the new contentItem into the tree
+ */
+ this.contentItems[index] = newChild;
+ newChild.parent = this;
+
+ /*
+ * Update tab reference
+ */
+ if (this.isStack) {
+ this.header.tabs[index].contentItem = newChild;
+ }
+
+ //TODO This doesn't update the config... refactor to leave item nodes untouched after creation
+ if (newChild.parent.isInitialised === true && newChild.isInitialised === false) {
+ newChild._$init();
+ }
+
+ this.callDownwards('setSize');
+ },
+
+ /**
+ * Convenience method.
+ * Shorthand for this.parent.removeChild( this )
+ *
+ * @returns {void}
+ */
+ remove: function () {
+ this.parent.removeChild(this);
+ },
+
+ /**
+ * Removes the component from the layout and creates a new
+ * browser window with the component and its children inside
+ *
+ * @returns {lm.controls.BrowserPopout}
+ */
+ popout: function () {
+ var browserPopout = this.layoutManager.createPopout(this);
+ this.emitBubblingEvent('stateChanged');
+ return browserPopout;
+ },
+
+ /**
+ * Maximises the Item or minimises it if it is already maximised
+ *
+ * @returns {void}
+ */
+ toggleMaximise: function (e) {
+ e && e.preventDefault();
+ if (this.isMaximised === true) {
+ this.layoutManager._$minimiseItem(this);
+ } else {
+ this.layoutManager._$maximiseItem(this);
+ }
+
+ this.isMaximised = !this.isMaximised;
+ this.emitBubblingEvent('stateChanged');
+ },
+
+ /**
+ * Selects the item if it is not already selected
+ *
+ * @returns {void}
+ */
+ select: function () {
+ if (this.layoutManager.selectedItem !== this) {
+ this.layoutManager.selectItem(this, true);
+ this.element.addClass('lm_selected');
+ }
+ },
+
+ /**
+ * De-selects the item if it is selected
+ *
+ * @returns {void}
+ */
+ deselect: function () {
+ if (this.layoutManager.selectedItem === this) {
+ this.layoutManager.selectedItem = null;
+ this.element.removeClass('lm_selected');
+ }
+ },
+
+ /**
+ * Set this component's title
+ *
+ * @public
+ * @param {String} title
+ *
+ * @returns {void}
+ */
+ setTitle: function (title) {
+ this.config.title = title;
+ this.emit('titleChanged', title);
+ this.emit('stateChanged');
+ },
+
+ /**
+ * Checks whether a provided id is present
+ *
+ * @public
+ * @param {String} id
+ *
+ * @returns {Boolean} isPresent
+ */
+ hasId: function (id) {
+ if (!this.config.id) {
+ return false;
+ } else if (typeof this.config.id === 'string') {
+ return this.config.id === id;
+ } else if (this.config.id instanceof Array) {
+ return lm.utils.indexOf(id, this.config.id) !== -1;
+ }
+ },
+
+ /**
+ * Adds an id. Adds it as a string if the component doesn't
+ * have an id yet or creates/uses an array
+ *
+ * @public
+ * @param {String} id
+ *
+ * @returns {void}
+ */
+ addId: function (id) {
+ if (this.hasId(id)) {
+ return;
+ }
+
+ if (!this.config.id) {
+ this.config.id = id;
+ } else if (typeof this.config.id === 'string') {
+ this.config.id = [this.config.id, id];
+ } else if (this.config.id instanceof Array) {
+ this.config.id.push(id);
+ }
+ },
+
+ /**
+ * Removes an existing id. Throws an error
+ * if the id is not present
+ *
+ * @public
+ * @param {String} id
+ *
+ * @returns {void}
+ */
+ removeId: function (id) {
+ if (!this.hasId(id)) {
+ throw new Error('Id not found');
+ }
+
+ if (typeof this.config.id === 'string') {
+ delete this.config.id;
+ } else if (this.config.id instanceof Array) {
+ var index = lm.utils.indexOf(id, this.config.id);
+ this.config.id.splice(index, 1);
+ }
+ },
+
+ /****************************************
+ * SELECTOR
+ ****************************************/
+ getItemsByFilter: function (filter) {
+ var result = [],
+ next = function (contentItem) {
+ for (var i = 0; i < contentItem.contentItems.length; i++) {
+
+ if (filter(contentItem.contentItems[i]) === true) {
+ result.push(contentItem.contentItems[i]);
+ }
+
+ next(contentItem.contentItems[i]);
+ }
+ };
+
+ next(this);
+ return result;
+ },
+
+ getItemsById: function (id) {
+ return this.getItemsByFilter(function (item) {
+ if (item.config.id instanceof Array) {
+ return lm.utils.indexOf(id, item.config.id) !== -1;
+ } else {
+ return item.config.id === id;
+ }
+ });
+ },
+
+ getItemsByType: function (type) {
+ return this._$getItemsByProperty('type', type);
+ },
+
+ getComponentsByName: function (componentName) {
+ var components = this._$getItemsByProperty('componentName', componentName),
+ instances = [],
+ i;
+
+ for (i = 0; i < components.length; i++) {
+ instances.push(components[i].instance);
+ }
+
+ return instances;
+ },
+
+ /****************************************
+ * PACKAGE PRIVATE
+ ****************************************/
+ _$getItemsByProperty: function (key, value) {
+ return this.getItemsByFilter(function (item) {
+ return item[key] === value;
+ });
+ },
+
+ _$setParent: function (parent) {
+ this.parent = parent;
+ },
+
+ _$highlightDropZone: function (x, y, area) {
+ this.layoutManager.dropTargetIndicator.highlightArea(area);
+ },
+
+ _$onDrop: function (contentItem) {
+ this.addChild(contentItem);
+ },
+
+ _$hide: function () {
+ this._callOnActiveComponents('hide');
+ this.element.hide();
+ this.layoutManager.updateSize();
+ },
+
+ _$show: function () {
+ this._callOnActiveComponents('show');
+ this.element.show();
+ this.layoutManager.updateSize();
+ },
+
+ _callOnActiveComponents: function (methodName) {
+ var stacks = this.getItemsByType('stack'),
+ activeContentItem,
+ i;
+
+ for (i = 0; i < stacks.length; i++) {
+ activeContentItem = stacks[i].getActiveContentItem();
+
+ if (activeContentItem && activeContentItem.isComponent) {
+ activeContentItem.container[methodName]();
+ }
+ }
+ },
+
+ /**
+ * Destroys this item ands its children
+ *
+ * @returns {void}
+ */
+ _$destroy: function () {
+ this.emitBubblingEvent('beforeItemDestroyed');
+ this.callDownwards('_$destroy', [], true, true);
+ this.element.remove();
+ this.emitBubblingEvent('itemDestroyed');
+ },
+
+ /**
+ * Returns the area the component currently occupies in the format
+ *
+ * {
+ * x1: int
+ * xy: int
+ * y1: int
+ * y2: int
+ * contentItem: contentItem
+ * }
+ */
+ _$getArea: function (element) {
+ element = element || this.element;
+
+ var offset = element.offset(),
+ width = element.width(),
+ height = element.height();
+
+ return {
+ x1: offset.left,
+ y1: offset.top,
+ x2: offset.left + width,
+ y2: offset.top + height,
+ surface: width * height,
+ contentItem: this
+ };
+ },
+
+ /**
+ * The tree of content items is created in two steps: First all content items are instantiated,
+ * then init is called recursively from top to bottem. This is the basic init function,
+ * it can be used, extended or overwritten by the content items
+ *
+ * Its behaviour depends on the content item
+ *
+ * @package private
+ *
+ * @returns {void}
+ */
+ _$init: function () {
+ var i;
+ this.setSize();
+
+ for (i = 0; i < this.contentItems.length; i++) {
+ this.childElementContainer.append(this.contentItems[i].element);
+ }
+
+ this.isInitialised = true;
+ this.emitBubblingEvent('itemCreated');
+ this.emitBubblingEvent(this.type + 'Created');
+ },
+
+ /**
+ * Emit an event that bubbles up the item tree.
+ *
+ * @param {String} name The name of the event
+ *
+ * @returns {void}
+ */
+ emitBubblingEvent: function (name) {
+ var event = new lm.utils.BubblingEvent(name, this);
+ this.emit(name, event);
+ },
+
+ /**
+ * Private method, creates all content items for this node at initialisation time
+ * PLEASE NOTE, please see addChild for adding contentItems add runtime
+ * @private
+ * @param {configuration item node} config
+ *
+ * @returns {void}
+ */
+ _createContentItems: function (config) {
+ var oContentItem, i;
+
+ if (!(config.content instanceof Array)) {
+ throw new lm.errors.ConfigurationError('content must be an Array', config);
+ }
+
+ for (i = 0; i < config.content.length; i++) {
+ oContentItem = this.layoutManager.createContentItem(config.content[i], this);
+ this.contentItems.push(oContentItem);
+ }
+ },
+
+ /**
+ * Extends an item configuration node with default settings
+ * @private
+ * @param {configuration item node} config
+ *
+ * @returns {configuration item node} extended config
+ */
+ _extendItemNode: function (config) {
+
+ for (var key in lm.config.itemDefaultConfig) {
+ if (config[key] === undefined) {
+ config[key] = lm.config.itemDefaultConfig[key];
+ }
+ }
+
+ return config;
+ },
+
+ /**
+ * Called for every event on the item tree. Decides whether the event is a bubbling
+ * event and propagates it to its parent
+ *
+ * @param {String} name the name of the event
+ * @param {lm.utils.BubblingEvent} event
+ *
+ * @returns {void}
+ */
+ _propagateEvent: function (name, event) {
+ if (event instanceof lm.utils.BubblingEvent &&
+ event.isPropagationStopped === false &&
+ this.isInitialised === true) {
+
+ /**
+ * In some cases (e.g. if an element is created from a DragSource) it
+ * doesn't have a parent and is not below root. If that's the case
+ * propagate the bubbling event from the top level of the substree directly
+ * to the layoutManager
+ */
+ if (this.isRoot === false && this.parent) {
+ this.parent.emit.apply(this.parent, Array.prototype.slice.call(arguments, 0));
+ } else {
+ this._scheduleEventPropagationToLayoutManager(name, event);
+ }
+ }
+ },
+
+ /**
+ * All raw events bubble up to the root element. Some events that
+ * are propagated to - and emitted by - the layoutManager however are
+ * only string-based, batched and sanitized to make them more usable
+ *
+ * @param {String} name the name of the event
+ *
+ * @private
+ * @returns {void}
+ */
+ _scheduleEventPropagationToLayoutManager: function (name, event) {
+ if (lm.utils.indexOf(name, this._throttledEvents) === -1) {
+ this.layoutManager.emit(name, event.origin);
+ } else {
+ if (this._pendingEventPropagations[name] !== true) {
+ this._pendingEventPropagations[name] = true;
+ lm.utils.animFrame(lm.utils.fnBind(this._propagateEventToLayoutManager, this, [name, event]));
+ }
+ }
+
+ },
+
+ /**
+ * Callback for events scheduled by _scheduleEventPropagationToLayoutManager
+ *
+ * @param {String} name the name of the event
+ *
+ * @private
+ * @returns {void}
+ */
+ _propagateEventToLayoutManager: function (name, event) {
+ this._pendingEventPropagations[name] = false;
+ this.layoutManager.emit(name, event);
+ }
+ });
+
+ /**
+ * @param {[type]} layoutManager [description]
+ * @param {[type]} config [description]
+ * @param {[type]} parent [description]
+ */
+ lm.items.Component = function (layoutManager, config, parent) {
+ lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+ var ComponentConstructor = layoutManager.getComponent(this.config.componentName),
+ componentConfig = $.extend(true, {}, this.config.componentState || {});
+
+ componentConfig.componentName = this.config.componentName;
+ this.componentName = this.config.componentName;
+
+ if (this.config.title === '') {
+ this.config.title = this.config.componentName;
+ }
+
+ this.isComponent = true;
+ this.container = new lm.container.ItemContainer(this.config, this, layoutManager);
+ this.instance = new ComponentConstructor(this.container, componentConfig);
+ this.element = this.container._element;
+ };
+
+ lm.utils.extend(lm.items.Component, lm.items.AbstractContentItem);
+
+ lm.utils.copy(lm.items.Component.prototype, {
+
+ close: function () {
+ this.parent.removeChild(this);
+ },
+
+ setSize: function () {
+ if (this.element.is(':visible')) {
+ // Do not update size of hidden components to prevent unwanted reflows
+ this.container._$setSize(this.element.width(), this.element.height());
+ }
+ },
+
+ _$init: function () {
+ lm.items.AbstractContentItem.prototype._$init.call(this);
+ this.container.emit('open');
+ },
+
+ _$hide: function () {
+ this.container.hide();
+ lm.items.AbstractContentItem.prototype._$hide.call(this);
+ },
+
+ _$show: function () {
+ this.container.show();
+ lm.items.AbstractContentItem.prototype._$show.call(this);
+ },
+
+ _$shown: function () {
+ this.container.shown();
+ lm.items.AbstractContentItem.prototype._$shown.call(this);
+ },
+
+ _$destroy: function () {
+ this.container.emit('destroy', this);
+ lm.items.AbstractContentItem.prototype._$destroy.call(this);
+ },
+
+ /**
+ * Dragging onto a component directly is not an option
+ *
+ * @returns null
+ */
+ _$getArea: function () {
+ return null;
+ }
+ });
+
+ lm.items.Root = function (layoutManager, config, containerElement) {
+ lm.items.AbstractContentItem.call(this, layoutManager, config, null);
+ this.isRoot = true;
+ this.type = 'root';
+ this.element = $('<div class="lm_goldenlayout lm_item lm_root"></div>');
+ this.childElementContainer = this.element;
+ this._containerElement = containerElement;
+ this._containerElement.append(this.element);
+ };
+
+ lm.utils.extend(lm.items.Root, lm.items.AbstractContentItem);
+
+ lm.utils.copy(lm.items.Root.prototype, {
+ addChild: function (contentItem) {
+ if (this.contentItems.length > 0) {
+ throw new Error('Root node can only have a single child');
+ }
+
+ contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+ this.childElementContainer.append(contentItem.element);
+ lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem);
+
+ this.callDownwards('setSize');
+ this.emitBubblingEvent('stateChanged');
+ },
+
+ setSize: function (width, height) {
+ width = (typeof width === 'undefined') ? this._containerElement.width() : width;
+ height = (typeof height === 'undefined') ? this._containerElement.height() : height;
+
+ this.element.width(width);
+ this.element.height(height);
+
+ /*
+ * Root can be empty
+ */
+ if (this.contentItems[0]) {
+ this.contentItems[0].element.width(width);
+ this.contentItems[0].element.height(height);
+ }
+ },
+ _$highlightDropZone: function (x, y, area) {
+ this.layoutManager.tabDropPlaceholder.remove();
+ lm.items.AbstractContentItem.prototype._$highlightDropZone.apply(this, arguments);
+ },
+
+ _$onDrop: function (contentItem, area) {
+ var stack;
+
+ if (contentItem.isComponent) {
+ stack = this.layoutManager.createContentItem({
+ type: 'stack',
+ header: contentItem.config.header || {}
+ }, this);
+ stack._$init();
+ stack.addChild(contentItem);
+ contentItem = stack;
+ }
+
+ if (!this.contentItems.length) {
+ this.addChild(contentItem);
+ } else {
+ var type = area.side[0] == 'x' ? 'row' : 'column';
+ var dimension = area.side[0] == 'x' ? 'width' : 'height';
+ var insertBefore = area.side[1] == '2';
+ var column = this.contentItems[0];
+ if (!column instanceof lm.items.RowOrColumn || column.type != type) {
+ var rowOrColumn = this.layoutManager.createContentItem({ type: type }, this);
+ this.replaceChild(column, rowOrColumn);
+ rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true);
+ rowOrColumn.addChild(column, insertBefore ? undefined : 0, true);
+ column.config[dimension] = 50;
+ contentItem.config[dimension] = 50;
+ rowOrColumn.callDownwards('setSize');
+ } else {
+ var sibbling = column.contentItems[insertBefore ? 0 : column.contentItems.length - 1]
+ column.addChild(contentItem, insertBefore ? 0 : undefined, true);
+ sibbling.config[dimension] *= 0.5;
+ contentItem.config[dimension] = sibbling.config[dimension];
+ column.callDownwards('setSize');
+ }
+ }
+ }
+ });
+
+
+
+ lm.items.RowOrColumn = function (isColumn, layoutManager, config, parent) {
+ lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+ this.isRow = !isColumn;
+ this.isColumn = isColumn;
+
+ this.element = $('<div class="lm_item lm_' + (isColumn ? 'column' : 'row') + '"></div>');
+ this.childElementContainer = this.element;
+ this._splitterSize = layoutManager.config.dimensions.borderWidth;
+ this._splitterGrabSize = layoutManager.config.dimensions.borderGrabWidth;
+ this._isColumn = isColumn;
+ this._dimension = isColumn ? 'height' : 'width';
+ this._splitter = [];
+ this._splitterPosition = null;
+ this._splitterMinPosition = null;
+ this._splitterMaxPosition = null;
+ };
+
+ lm.utils.extend(lm.items.RowOrColumn, lm.items.AbstractContentItem);
+
+ lm.utils.copy(lm.items.RowOrColumn.prototype, {
+
+ /**
+ * Add a new contentItem to the Row or Column
+ *
+ * @param {lm.item.AbstractContentItem} contentItem
+ * @param {[int]} index The position of the new item within the Row or Column.
+ * If no index is provided the item will be added to the end
+ * @param {[bool]} _$suspendResize If true the items won't be resized. This will leave the item in
+ * an inconsistent state and is only intended to be used if multiple
+ * children need to be added in one go and resize is called afterwards
+ *
+ * @returns {void}
+ */
+ addChild: function (contentItem, index, _$suspendResize) {
+
+ var newItemSize, itemSize, i, splitterElement;
+
+ contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+
+ if (index === undefined) {
+ index = this.contentItems.length;
+ }
+
+ if (this.contentItems.length > 0) {
+ splitterElement = this._createSplitter(Math.max(0, index - 1)).element;
+
+ if (index > 0) {
+ this.contentItems[index - 1].element.after(splitterElement);
+ splitterElement.after(contentItem.element);
+ } else {
+ this.contentItems[0].element.before(splitterElement);
+ splitterElement.before(contentItem.element);
+ }
+ } else {
+ this.childElementContainer.append(contentItem.element);
+ }
+
+ lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem, index);
+
+ let fixedItemSize = 0;
+ let variableItemCount = 0;
+ for (i = 0; i < this.contentItems.length; i++) {
+ if (this.contentItems[i].config.fixed)
+ fixedItemSize += this.contentItems[i].config[this._dimension];
+ else variableItemCount++;
+ }
+
+ newItemSize = (1 / variableItemCount) * (100 - fixedItemSize);
+
+ if (_$suspendResize === true) {
+ this.emitBubblingEvent('stateChanged');
+ return;
+ }
+
+ for (i = 0; i < this.contentItems.length; i++) {
+ if (this.contentItems[i].config.fixed)
+ ;
+ else if (this.contentItems[i] === contentItem) {
+ contentItem.config[this._dimension] = newItemSize;
+ } else {
+ itemSize = this.contentItems[i].config[this._dimension] *= (100 - newItemSize - fixedItemSize) / (100 - fixedItemSize);
+ this.contentItems[i].config[this._dimension] = itemSize;
+ }
+ }
+
+ this.callDownwards('setSize');
+ this.emitBubblingEvent('stateChanged');
+
+ },
+
+ /**
+ * Removes a child of this element
+ *
+ * @param {lm.items.AbstractContentItem} contentItem
+ * @param {boolean} keepChild If true the child will be removed, but not destroyed
+ *
+ * @returns {void}
+ */
+ removeChild: function (contentItem, keepChild) {
+ var removedItemSize = contentItem.config[this._dimension],
+ index = lm.utils.indexOf(contentItem, this.contentItems),
+ splitterIndex = Math.max(index - 1, 0),
+ i,
+ childItem;
+
+ if (index === -1) {
+ throw new Error('Can\'t remove child. ContentItem is not child of this Row or Column');
+ }
+
+ /**
+ * Remove the splitter before the item or after if the item happens
+ * to be the first in the row/column
+ */
+ if (this._splitter[splitterIndex]) {
+ this._splitter[splitterIndex]._$destroy();
+ this._splitter.splice(splitterIndex, 1);
+ }
+
+ let fixedItemSize = 0;
+ for (i = 0; i < this.contentItems.length; i++) {
+ if (this.contentItems[i].config.fixed)
+ fixedItemSize += this.contentItems[i].config[this._dimension];
+ }
+ /**
+ * Allocate the space that the removed item occupied to the remaining items
+ */
+ for (i = 0; i < this.contentItems.length; i++) {
+ if (this.contentItems[i].config.fixed)
+ ;
+ else if (this.contentItems[i] !== contentItem) {
+ this.contentItems[i].config[this._dimension] *= (100 - fixedItemSize) / (100 - removedItemSize - fixedItemSize);
+ }
+ }
+
+ lm.items.AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild);
+
+ if (this.contentItems.length === 1 && this.config.isClosable === true) {
+ childItem = this.contentItems[0];
+ this.contentItems = [];
+ this.parent.replaceChild(this, childItem, true);
+ } else {
+ this.callDownwards('setSize');
+ this.emitBubblingEvent('stateChanged');
+ }
+ },
+
+ /**
+ * Replaces a child of this Row or Column with another contentItem
+ *
+ * @param {lm.items.AbstractContentItem} oldChild
+ * @param {lm.items.AbstractContentItem} newChild
+ *
+ * @returns {void}
+ */
+ replaceChild: function (oldChild, newChild) {
+ var size = oldChild.config[this._dimension];
+ lm.items.AbstractContentItem.prototype.replaceChild.call(this, oldChild, newChild);
+ newChild.config[this._dimension] = size;
+ this.callDownwards('setSize');
+ this.emitBubblingEvent('stateChanged');
+ },
+
+ /**
+ * Called whenever the dimensions of this item or one of its parents change
+ *
+ * @returns {void}
+ */
+ setSize: function () {
+ if (this.contentItems.length > 0) {
+ this._calculateRelativeSizes();
+ this._setAbsoluteSizes();
+ }
+ this.emitBubblingEvent('stateChanged');
+ this.emit('resize');
+ },
+
+ /**
+ * Invoked recursively by the layout manager. AbstractContentItem.init appends
+ * the contentItem's DOM elements to the container, RowOrColumn init adds splitters
+ * in between them
+ *
+ * @package private
+ * @override AbstractContentItem._$init
+ * @returns {void}
+ */
+ _$init: function () {
+ if (this.isInitialised === true) return;
+
+ var i;
+
+ lm.items.AbstractContentItem.prototype._$init.call(this);
+
+ for (i = 0; i < this.contentItems.length - 1; i++) {
+ this.contentItems[i].element.after(this._createSplitter(i).element);
+ }
+ },
+
+ /**
+ * Turns the relative sizes calculated by _calculateRelativeSizes into
+ * absolute pixel values and applies them to the children's DOM elements
+ *
+ * Assigns additional pixels to counteract Math.floor
+ *
+ * @private
+ * @returns {void}
+ */
+ _setAbsoluteSizes: function () {
+ var i,
+ sizeData = this._calculateAbsoluteSizes();
+
+ for (i = 0; i < this.contentItems.length; i++) {
+ if (sizeData.additionalPixel - i > 0) {
+ sizeData.itemSizes[i]++;
+ }
+
+ if (this._isColumn) {
+ this.contentItems[i].element.width(sizeData.totalWidth);
+ this.contentItems[i].element.height(sizeData.itemSizes[i]);
+ } else {
+ this.contentItems[i].element.width(sizeData.itemSizes[i]);
+ this.contentItems[i].element.height(sizeData.totalHeight);
+ }
+ }
+ },
+
+ /**
+ * Calculates the absolute sizes of all of the children of this Item.
+ * @returns {object} - Set with absolute sizes and additional pixels.
+ */
+ _calculateAbsoluteSizes: function () {
+ var i,
+ totalSplitterSize = (this.contentItems.length - 1) * this._splitterSize,
+ totalWidth = this.element.width(),
+ totalHeight = this.element.height(),
+ totalAssigned = 0,
+ additionalPixel,
+ itemSize,
+ itemSizes = [];
+
+ if (this._isColumn) {
+ totalHeight -= totalSplitterSize;
+ } else {
+ totalWidth -= totalSplitterSize;
+ }
+
+ for (i = 0; i < this.contentItems.length; i++) {
+ if (this._isColumn) {
+ itemSize = Math.floor(totalHeight * (this.contentItems[i].config.height / 100));
+ } else {
+ itemSize = Math.floor(totalWidth * (this.contentItems[i].config.width / 100));
+ }
+
+ totalAssigned += itemSize;
+ itemSizes.push(itemSize);
+ }
+
+ additionalPixel = Math.floor((this._isColumn ? totalHeight : totalWidth) - totalAssigned);
+
+ return {
+ itemSizes: itemSizes,
+ additionalPixel: additionalPixel,
+ totalWidth: totalWidth,
+ totalHeight: totalHeight
+ };
+ },
+
+ /**
+ * Calculates the relative sizes of all children of this Item. The logic
+ * is as follows:
+ *
+ * - Add up the total size of all items that have a configured size
+ *
+ * - If the total == 100 (check for floating point errors)
+ * Excellent, job done
+ *
+ * - If the total is > 100,
+ * set the size of items without set dimensions to 1/3 and add this to the total
+ * set the size off all items so that the total is hundred relative to their original size
+ *
+ * - If the total is < 100
+ * If there are items without set dimensions, distribute the remainder to 100 evenly between them
+ * If there are no items without set dimensions, increase all items sizes relative to
+ * their original size so that they add up to 100
+ *
+ * @private
+ * @returns {void}
+ */
+ _calculateRelativeSizes: function () {
+
+ var i,
+ total = 0,
+ itemsWithoutSetDimension = [],
+ dimension = this._isColumn ? 'height' : 'width';
+
+ for (i = 0; i < this.contentItems.length; i++) {
+ if (this.contentItems[i].config[dimension] !== undefined) {
+ total += this.contentItems[i].config[dimension];
+ } else {
+ itemsWithoutSetDimension.push(this.contentItems[i]);
+ }
+ }
+
+ /**
+ * Everything adds up to hundred, all good :-)
+ */
+ if (Math.round(total) === 100) {
+ this._respectMinItemWidth();
+ return;
+ }
+
+ /**
+ * Allocate the remaining size to the items without a set dimension
+ */
+ if (Math.round(total) < 100 && itemsWithoutSetDimension.length > 0) {
+ for (i = 0; i < itemsWithoutSetDimension.length; i++) {
+ itemsWithoutSetDimension[i].config[dimension] = (100 - total) / itemsWithoutSetDimension.length;
+ }
+ this._respectMinItemWidth();
+ return;
+ }
+
+ /**
+ * If the total is > 100, but there are also items without a set dimension left, assing 50
+ * as their dimension and add it to the total
+ *
+ * This will be reset in the next step
+ */
+ if (Math.round(total) > 100) {
+ for (i = 0; i < itemsWithoutSetDimension.length; i++) {
+ itemsWithoutSetDimension[i].config[dimension] = 50;
+ total += 50;
+ }
+ }
+
+ /**
+ * Set every items size relative to 100 relative to its size to total
+ */
+ for (i = 0; i < this.contentItems.length; i++) {
+ this.contentItems[i].config[dimension] = (this.contentItems[i].config[dimension] / total) * 100;
+ }
+
+ this._respectMinItemWidth();
+ },
+
+ /**
+ * Adjusts the column widths to respect the dimensions minItemWidth if set.
+ * @returns {}
+ */
+ _respectMinItemWidth: function () {
+ var minItemWidth = this.layoutManager.config.dimensions ? (this.layoutManager.config.dimensions.minItemWidth || 0) : 0,
+ sizeData = null,
+ entriesOverMin = [],
+ totalOverMin = 0,
+ totalUnderMin = 0,
+ remainingWidth = 0,
+ itemSize = 0,
+ contentItem = null,
+ reducePercent,
+ reducedWidth,
+ allEntries = [],
+ entry;
+
+ if (this._isColumn || !minItemWidth || this.contentItems.length <= 1) {
+ return;
+ }
+
+ sizeData = this._calculateAbsoluteSizes();
+
+ /**
+ * Figure out how much we are under the min item size total and how much room we have to use.
+ */
+ for (var i = 0; i < this.contentItems.length; i++) {
+
+ contentItem = this.contentItems[i];
+ itemSize = sizeData.itemSizes[i];
+
+ if (itemSize < minItemWidth) {
+ totalUnderMin += minItemWidth - itemSize;
+ entry = { width: minItemWidth };
+
+ }
+ else {
+ totalOverMin += itemSize - minItemWidth;
+ entry = { width: itemSize };
+ entriesOverMin.push(entry);
+ }
+
+ allEntries.push(entry);
+ }
+
+ /**
+ * If there is nothing under min, or there is not enough over to make up the difference, do nothing.
+ */
+ if (totalUnderMin === 0 || totalUnderMin > totalOverMin) {
+ return;
+ }
+
+ /**
+ * Evenly reduce all columns that are over the min item width to make up the difference.
+ */
+ reducePercent = totalUnderMin / totalOverMin;
+ remainingWidth = totalUnderMin;
+ for (i = 0; i < entriesOverMin.length; i++) {
+ entry = entriesOverMin[i];
+ reducedWidth = Math.round((entry.width - minItemWidth) * reducePercent);
+ remainingWidth -= reducedWidth;
+ entry.width -= reducedWidth;
+ }
+
+ /**
+ * Take anything remaining from the last item.
+ */
+ if (remainingWidth !== 0) {
+ allEntries[allEntries.length - 1].width -= remainingWidth;
+ }
+
+ /**
+ * Set every items size relative to 100 relative to its size to total
+ */
+ for (i = 0; i < this.contentItems.length; i++) {
+ this.contentItems[i].config.width = (allEntries[i].width / sizeData.totalWidth) * 100;
+ }
+ },
+
+ /**
+ * Instantiates a new lm.controls.Splitter, binds events to it and adds
+ * it to the array of splitters at the position specified as the index argument
+ *
+ * What it doesn't do though is append the splitter to the DOM
+ *
+ * @param {Int} index The position of the splitter
+ *
+ * @returns {lm.controls.Splitter}
+ */
+ _createSplitter: function (index) {
+ var splitter;
+ splitter = new lm.controls.Splitter(this._isColumn, this._splitterSize, this._splitterGrabSize);
+ splitter.on('drag', lm.utils.fnBind(this._onSplitterDrag, this, [splitter]), this);
+ splitter.on('dragStop', lm.utils.fnBind(this._onSplitterDragStop, this, [splitter]), this);
+ splitter.on('dragStart', lm.utils.fnBind(this._onSplitterDragStart, this, [splitter]), this);
+ this._splitter.splice(index, 0, splitter);
+ return splitter;
+ },
+
+ /**
+ * Locates the instance of lm.controls.Splitter in the array of
+ * registered splitters and returns a map containing the contentItem
+ * before and after the splitters, both of which are affected if the
+ * splitter is moved
+ *
+ * @param {lm.controls.Splitter} splitter
+ *
+ * @returns {Object} A map of contentItems that the splitter affects
+ */
+ _getItemsForSplitter: function (splitter) {
+ var index = lm.utils.indexOf(splitter, this._splitter);
+
+ return {
+ before: this.contentItems[index],
+ after: this.contentItems[index + 1]
+ };
+ },
+
+ /**
+ * Gets the minimum dimensions for the given item configuration array
+ * @param item
+ * @private
+ */
+ _getMinimumDimensions: function (arr) {
+ var minWidth = 0, minHeight = 0;
+
+ for (var i = 0; i < arr.length; ++i) {
+ minWidth = Math.max(arr[i].minWidth || 0, minWidth);
+ minHeight = Math.max(arr[i].minHeight || 0, minHeight);
+ }
+
+ return { horizontal: minWidth, vertical: minHeight };
+ },
+
+ /**
+ * Invoked when a splitter's dragListener fires dragStart. Calculates the splitters
+ * movement area once (so that it doesn't need calculating on every mousemove event)
+ *
+ * @param {lm.controls.Splitter} splitter
+ *
+ * @returns {void}
+ */
+ _onSplitterDragStart: function (splitter) {
+ var items = this._getItemsForSplitter(splitter),
+ minSize = this.layoutManager.config.dimensions[this._isColumn ? 'minItemHeight' : 'minItemWidth'];
+
+ var beforeMinDim = this._getMinimumDimensions(items.before.config.content);
+ var beforeMinSize = this._isColumn ? beforeMinDim.vertical : beforeMinDim.horizontal;
+
+ var afterMinDim = this._getMinimumDimensions(items.after.config.content);
+ var afterMinSize = this._isColumn ? afterMinDim.vertical : afterMinDim.horizontal;
+
+ this._splitterPosition = 0;
+ this._splitterMinPosition = -1 * (items.before.element[this._dimension]() - (beforeMinSize || minSize));
+ this._splitterMaxPosition = items.after.element[this._dimension]() - (afterMinSize || minSize);
+ },
+
+ /**
+ * Invoked when a splitter's DragListener fires drag. Updates the splitters DOM position,
+ * but not the sizes of the elements the splitter controls in order to minimize resize events
+ *
+ * @param {lm.controls.Splitter} splitter
+ * @param {Int} offsetX Relative pixel values to the splitters original position. Can be negative
+ * @param {Int} offsetY Relative pixel values to the splitters original position. Can be negative
+ *
+ * @returns {void}
+ */
+ _onSplitterDrag: function (splitter, offsetX, offsetY) {
+ var offset = this._isColumn ? offsetY : offsetX;
+
+ if (offset > this._splitterMinPosition && offset < this._splitterMaxPosition) {
+ this._splitterPosition = offset;
+ splitter.element.css(this._isColumn ? 'top' : 'left', offset);
+ }
+ },
+
+ /**
+ * Invoked when a splitter's DragListener fires dragStop. Resets the splitters DOM position,
+ * and applies the new sizes to the elements before and after the splitter and their children
+ * on the next animation frame
+ *
+ * @param {lm.controls.Splitter} splitter
+ *
+ * @returns {void}
+ */
+ _onSplitterDragStop: function (splitter) {
+
+ var items = this._getItemsForSplitter(splitter),
+ sizeBefore = items.before.element[this._dimension](),
+ sizeAfter = items.after.element[this._dimension](),
+ splitterPositionInRange = (this._splitterPosition + sizeBefore) / (sizeBefore + sizeAfter),
+ totalRelativeSize = items.before.config[this._dimension] + items.after.config[this._dimension];
+
+ items.before.config[this._dimension] = splitterPositionInRange * totalRelativeSize;
+ items.after.config[this._dimension] = (1 - splitterPositionInRange) * totalRelativeSize;
+
+ splitter.element.css({
+ 'top': 0,
+ 'left': 0
+ });
+
+ lm.utils.animFrame(lm.utils.fnBind(this.callDownwards, this, ['setSize']));
+ }
+ });
+
+ lm.items.Stack = function (layoutManager, config, parent) {
+ lm.items.AbstractContentItem.call(this, layoutManager, config, parent);
+
+ this.element = $('<div class="lm_item lm_stack"></div>');
+ this._activeContentItem = null;
+ var cfg = layoutManager.config;
+ this._header = { // defaults' reconstruction from old configuration style
+ show: cfg.settings.hasHeaders === true && config.hasHeaders !== false,
+ popout: cfg.settings.showPopoutIcon && cfg.labels.popout,
+ maximise: cfg.settings.showMaximiseIcon && cfg.labels.maximise,
+ close: cfg.settings.showCloseIcon && cfg.labels.close,
+ minimise: cfg.labels.minimise,
+ };
+ if (cfg.header) // load simplified version of header configuration (https://github.com/deepstreamIO/golden-layout/pull/245)
+ lm.utils.copy(this._header, cfg.header);
+ if (config.header) // load from stack
+ lm.utils.copy(this._header, config.header);
+ if (config.content && config.content[0] && config.content[0].header) // load from component if stack omitted
+ lm.utils.copy(this._header, config.content[0].header);
+
+ this._dropZones = {};
+ this._dropSegment = null;
+ this._contentAreaDimensions = null;
+ this._dropIndex = null;
+
+ this.isStack = true;
+
+ this.childElementContainer = $('<div class="lm_items"></div>');
+ this.header = new lm.controls.Header(layoutManager, this);
+
+ this.element.append(this.header.element);
+ this.element.append(this.childElementContainer);
+ this._setupHeaderPosition();
+ this._$validateClosability();
+ };
+
+ lm.utils.extend(lm.items.Stack, lm.items.AbstractContentItem);
+
+ lm.utils.copy(lm.items.Stack.prototype, {
+
+ setSize: function () {
+ var i,
+ headerSize = this._header.show ? this.layoutManager.config.dimensions.headerHeight : 0,
+ contentWidth = this.element.width() - (this._sided ? headerSize : 0),
+ contentHeight = this.element.height() - (!this._sided ? headerSize : 0);
+
+ this.childElementContainer.width(contentWidth);
+ this.childElementContainer.height(contentHeight);
+
+ for (i = 0; i < this.contentItems.length; i++) {
+ this.contentItems[i].element.width(contentWidth).height(contentHeight);
+ }
+ this.emit('resize');
+ this.emitBubblingEvent('stateChanged');
+ },
+
+ _$init: function () {
+ var i, initialItem;
+
+ if (this.isInitialised === true) return;
+
+ lm.items.AbstractContentItem.prototype._$init.call(this);
+
+ for (i = 0; i < this.contentItems.length; i++) {
+ this.header.createTab(this.contentItems[i]);
+ this.contentItems[i]._$hide();
+ }
+
+ if (this.contentItems.length > 0) {
+ initialItem = this.contentItems[this.config.activeItemIndex || 0];
+
+ if (!initialItem) {
+ throw new Error('Configured activeItemIndex out of bounds');
+ }
+
+ this.setActiveContentItem(initialItem);
+ }
+ },
+
+ setActiveContentItem: function (contentItem) {
+ if (lm.utils.indexOf(contentItem, this.contentItems) === -1) {
+ throw new Error('contentItem is not a child of this stack');
+ }
+
+ if (this._activeContentItem !== null) {
+ this._activeContentItem._$hide();
+ }
+
+ this._activeContentItem = contentItem;
+ this.header.setActiveContentItem(contentItem);
+ contentItem._$show();
+ this.emit('activeContentItemChanged', contentItem);
+ this.layoutManager.emit('activeContentItemChanged', contentItem);
+ this.emitBubblingEvent('stateChanged');
+ },
+
+ getActiveContentItem: function () {
+ return this.header.activeContentItem;
+ },
+
+ addChild: function (contentItem, index) {
+ contentItem = this.layoutManager._$normalizeContentItem(contentItem, this);
+ lm.items.AbstractContentItem.prototype.addChild.call(this, contentItem, index);
+ this.childElementContainer.append(contentItem.element);
+ this.header.createTab(contentItem, index);
+ this.setActiveContentItem(contentItem);
+ this.callDownwards('setSize');
+ this._$validateClosability();
+ this.emitBubblingEvent('stateChanged');
+ },
+
+ removeChild: function (contentItem, keepChild) {
+ var index = lm.utils.indexOf(contentItem, this.contentItems);
+ lm.items.AbstractContentItem.prototype.removeChild.call(this, contentItem, keepChild);
+ this.header.removeTab(contentItem);
+ if (this.header.activeContentItem === contentItem) {
+ if (this.contentItems.length > 0) {
+ this.setActiveContentItem(this.contentItems[Math.max(index - 1, 0)]);
+ } else {
+ this._activeContentItem = null;
+ }
+ }
+
+ this._$validateClosability();
+ this.emitBubblingEvent('stateChanged');
+ },
+
+ /**
+ * Validates that the stack is still closable or not. If a stack is able
+ * to close, but has a non closable component added to it, the stack is no
+ * longer closable until all components are closable.
+ *
+ * @returns {void}
+ */
+ _$validateClosability: function () {
+ var contentItem,
+ isClosable,
+ len,
+ i;
+
+ isClosable = this.header._isClosable();
+
+ for (i = 0, len = this.contentItems.length; i < len; i++) {
+ if (!isClosable) {
+ break;
+ }
+
+ isClosable = this.contentItems[i].config.isClosable;
+ }
+
+ this.header._$setClosable(isClosable);
+ },
+
+ _$destroy: function () {
+ lm.items.AbstractContentItem.prototype._$destroy.call(this);
+ this.header._$destroy();
+ },
+
+
+ /**
+ * Ok, this one is going to be the tricky one: The user has dropped {contentItem} onto this stack.
+ *
+ * It was dropped on either the stacks header or the top, right, bottom or left bit of the content area
+ * (which one of those is stored in this._dropSegment). Now, if the user has dropped on the header the case
+ * is relatively clear: We add the item to the existing stack... job done (might be good to have
+ * tab reordering at some point, but lets not sweat it right now)
+ *
+ * If the item was dropped on the content part things are a bit more complicated. If it was dropped on either the
+ * top or bottom region we need to create a new column and place the items accordingly.
+ * Unless, of course if the stack is already within a column... in which case we want
+ * to add the newly created item to the existing column...
+ * either prepend or append it, depending on wether its top or bottom.
+ *
+ * Same thing for rows and left / right drop segments... so in total there are 9 things that can potentially happen
+ * (left, top, right, bottom) * is child of the right parent (row, column) + header drop
+ *
+ * @param {lm.item} contentItem
+ *
+ * @returns {void}
+ */
+ _$onDrop: function (contentItem) {
+
+ /*
+ * The item was dropped on the header area. Just add it as a child of this stack and
+ * get the hell out of this logic
+ */
+ if (this._dropSegment === 'header') {
+ this._resetHeaderDropZone();
+ this.addChild(contentItem, this._dropIndex);
+ return;
+ }
+
+ /*
+ * The stack is empty. Let's just add the element.
+ */
+ if (this._dropSegment === 'body') {
+ this.addChild(contentItem);
+ return;
+ }
+
+ /*
+ * The item was dropped on the top-, left-, bottom- or right- part of the content. Let's
+ * aggregate some conditions to make the if statements later on more readable
+ */
+ var isVertical = this._dropSegment === 'top' || this._dropSegment === 'bottom',
+ isHorizontal = this._dropSegment === 'left' || this._dropSegment === 'right',
+ insertBefore = this._dropSegment === 'top' || this._dropSegment === 'left',
+ hasCorrectParent = (isVertical && this.parent.isColumn) || (isHorizontal && this.parent.isRow),
+ type = isVertical ? 'column' : 'row',
+ dimension = isVertical ? 'height' : 'width',
+ index,
+ stack,
+ rowOrColumn;
+
+ /*
+ * The content item can be either a component or a stack. If it is a component, wrap it into a stack
+ */
+ if (contentItem.isComponent) {
+ stack = this.layoutManager.createContentItem({
+ type: 'stack',
+ header: contentItem.config.header || {}
+ }, this);
+ stack._$init();
+ stack.addChild(contentItem);
+ contentItem = stack;
+ }
+
+ /*
+ * If the item is dropped on top or bottom of a column or left and right of a row, it's already
+ * layd out in the correct way. Just add it as a child
+ */
+ if (hasCorrectParent) {
+ index = lm.utils.indexOf(this, this.parent.contentItems);
+ this.parent.addChild(contentItem, insertBefore ? index : index + 1, true);
+ this.config[dimension] *= 0.5;
+ contentItem.config[dimension] = this.config[dimension];
+ this.parent.callDownwards('setSize');
+ /*
+ * This handles items that are dropped on top or bottom of a row or left / right of a column. We need
+ * to create the appropriate contentItem for them to live in
+ */
+ } else {
+ type = isVertical ? 'column' : 'row';
+ rowOrColumn = this.layoutManager.createContentItem({ type: type }, this);
+ this.parent.replaceChild(this, rowOrColumn);
+
+ rowOrColumn.addChild(contentItem, insertBefore ? 0 : undefined, true);
+ rowOrColumn.addChild(this, insertBefore ? undefined : 0, true);
+
+ this.config[dimension] = 50;
+ contentItem.config[dimension] = 50;
+ rowOrColumn.callDownwards('setSize');
+ }
+ },
+
+ /**
+ * If the user hovers above the header part of the stack, indicate drop positions for tabs.
+ * otherwise indicate which segment of the body the dragged item would be dropped on
+ *
+ * @param {Int} x Absolute Screen X
+ * @param {Int} y Absolute Screen Y
+ *
+ * @returns {void}
+ */
+ _$highlightDropZone: function (x, y) {
+ var segment, area;
+
+ for (segment in this._contentAreaDimensions) {
+ area = this._contentAreaDimensions[segment].hoverArea;
+
+ if (area.x1 < x && area.x2 > x && area.y1 < y && area.y2 > y) {
+
+ if (segment === 'header') {
+ this._dropSegment = 'header';
+ this._highlightHeaderDropZone(this._sided ? y : x);
+ } else {
+ this._resetHeaderDropZone();
+ this._highlightBodyDropZone(segment);
+ }
+
+ return;
+ }
+ }
+ },
+
+ _$getArea: function () {
+ if (this.element.is(':visible') === false) {
+ return null;
+ }
+
+ var getArea = lm.items.AbstractContentItem.prototype._$getArea,
+ headerArea = getArea.call(this, this.header.element),
+ contentArea = getArea.call(this, this.childElementContainer),
+ contentWidth = contentArea.x2 - contentArea.x1,
+ contentHeight = contentArea.y2 - contentArea.y1;
+
+ this._contentAreaDimensions = {
+ header: {
+ hoverArea: {
+ x1: headerArea.x1,
+ y1: headerArea.y1,
+ x2: headerArea.x2,
+ y2: headerArea.y2
+ },
+ highlightArea: {
+ x1: headerArea.x1,
+ y1: headerArea.y1,
+ x2: headerArea.x2,
+ y2: headerArea.y2
+ }
+ }
+ };
+
+ /**
+ * If this Stack is a parent to rows, columns or other stacks only its
+ * header is a valid dropzone.
+ */
+ if (this._activeContentItem && this._activeContentItem.isComponent === false) {
+ return headerArea;
+ }
+
+ /**
+ * Highlight the entire body if the stack is empty
+ */
+ if (this.contentItems.length === 0) {
+
+ this._contentAreaDimensions.body = {
+ hoverArea: {
+ x1: contentArea.x1,
+ y1: contentArea.y1,
+ x2: contentArea.x2,
+ y2: contentArea.y2
+ },
+ highlightArea: {
+ x1: contentArea.x1,
+ y1: contentArea.y1,
+ x2: contentArea.x2,
+ y2: contentArea.y2
+ }
+ };
+
+ return getArea.call(this, this.element);
+ }
+
+ this._contentAreaDimensions.left = {
+ hoverArea: {
+ x1: contentArea.x1,
+ y1: contentArea.y1,
+ x2: contentArea.x1 + contentWidth * 0.25,
+ y2: contentArea.y2
+ },
+ highlightArea: {
+ x1: contentArea.x1,
+ y1: contentArea.y1,
+ x2: contentArea.x1 + contentWidth * 0.5,
+ y2: contentArea.y2
+ }
+ };
+
+ this._contentAreaDimensions.top = {
+ hoverArea: {
+ x1: contentArea.x1 + contentWidth * 0.25,
+ y1: contentArea.y1,
+ x2: contentArea.x1 + contentWidth * 0.75,
+ y2: contentArea.y1 + contentHeight * 0.5
+ },
+ highlightArea: {
+ x1: contentArea.x1,
+ y1: contentArea.y1,
+ x2: contentArea.x2,
+ y2: contentArea.y1 + contentHeight * 0.5
+ }
+ };
+
+ this._contentAreaDimensions.right = {
+ hoverArea: {
+ x1: contentArea.x1 + contentWidth * 0.75,
+ y1: contentArea.y1,
+ x2: contentArea.x2,
+ y2: contentArea.y2
+ },
+ highlightArea: {
+ x1: contentArea.x1 + contentWidth * 0.5,
+ y1: contentArea.y1,
+ x2: contentArea.x2,
+ y2: contentArea.y2
+ }
+ };
+
+ this._contentAreaDimensions.bottom = {
+ hoverArea: {
+ x1: contentArea.x1 + contentWidth * 0.25,
+ y1: contentArea.y1 + contentHeight * 0.5,
+ x2: contentArea.x1 + contentWidth * 0.75,
+ y2: contentArea.y2
+ },
+ highlightArea: {
+ x1: contentArea.x1,
+ y1: contentArea.y1 + contentHeight * 0.5,
+ x2: contentArea.x2,
+ y2: contentArea.y2
+ }
+ };
+
+ return getArea.call(this, this.element);
+ },
+
+ _highlightHeaderDropZone: function (x) {
+ var i,
+ tabElement,
+ tabsLength = this.header.tabs.length,
+ isAboveTab = false,
+ tabTop,
+ tabLeft,
+ offset,
+ placeHolderLeft,
+ headerOffset,
+ tabWidth,
+ halfX;
+
+ // Empty stack
+ if (tabsLength === 0) {
+ headerOffset = this.header.element.offset();
+
+ this.layoutManager.dropTargetIndicator.highlightArea({
+ x1: headerOffset.left,
+ x2: headerOffset.left + 100,
+ y1: headerOffset.top + this.header.element.height() - 20,
+ y2: headerOffset.top + this.header.element.height()
+ });
+
+ return;
+ }
+
+ for (i = 0; i < tabsLength; i++) {
+ tabElement = this.header.tabs[i].element;
+ offset = tabElement.offset();
+ if (this._sided) {
+ tabLeft = offset.top;
+ tabTop = offset.left;
+ tabWidth = tabElement.height();
+ } else {
+ tabLeft = offset.left;
+ tabTop = offset.top;
+ tabWidth = tabElement.width();
+ }
+
+ if (x > tabLeft && x < tabLeft + tabWidth) {
+ isAboveTab = true;
+ break;
+ }
+ }
+
+ if (isAboveTab === false && x < tabLeft) {
+ return;
+ }
+
+ halfX = tabLeft + tabWidth / 2;
+
+ if (x < halfX) {
+ this._dropIndex = i;
+ tabElement.before(this.layoutManager.tabDropPlaceholder);
+ } else {
+ this._dropIndex = Math.min(i + 1, tabsLength);
+ tabElement.after(this.layoutManager.tabDropPlaceholder);
+ }
+
+
+ if (this._sided) {
+ placeHolderTop = this.layoutManager.tabDropPlaceholder.offset().top;
+ this.layoutManager.dropTargetIndicator.highlightArea({
+ x1: tabTop,
+ x2: tabTop + tabElement.innerHeight(),
+ y1: placeHolderTop,
+ y2: placeHolderTop + this.layoutManager.tabDropPlaceholder.width()
+ });
+ return;
+ }
+ placeHolderLeft = this.layoutManager.tabDropPlaceholder.offset().left;
+
+ this.layoutManager.dropTargetIndicator.highlightArea({
+ x1: placeHolderLeft,
+ x2: placeHolderLeft + this.layoutManager.tabDropPlaceholder.width(),
+ y1: tabTop,
+ y2: tabTop + tabElement.innerHeight()
+ });
+ },
+
+ _resetHeaderDropZone: function () {
+ this.layoutManager.tabDropPlaceholder.remove();
+ },
+
+ _setupHeaderPosition: function () {
+ var side = ['right', 'left', 'bottom'].indexOf(this._header.show) >= 0 && this._header.show;
+ this.header.element.toggle(!!this._header.show);
+ this._side = side;
+ this._sided = ['right', 'left'].indexOf(this._side) >= 0;
+ this.element.removeClass('lm_left lm_right lm_bottom');
+ if (this._side)
+ this.element.addClass('lm_' + this._side);
+ if (this.element.find('.lm_header').length && this.childElementContainer) {
+ var headerPosition = ['right', 'bottom'].indexOf(this._side) >= 0 ? 'before' : 'after';
+ this.header.element[headerPosition](this.childElementContainer);
+ this.callDownwards('setSize');
+ }
+ },
+
+ _highlightBodyDropZone: function (segment) {
+ var highlightArea = this._contentAreaDimensions[segment].highlightArea;
+ this.layoutManager.dropTargetIndicator.highlightArea(highlightArea);
+ this._dropSegment = segment;
+ }
+ });
+
+ lm.utils.BubblingEvent = function (name, origin) {
+ this.name = name;
+ this.origin = origin;
+ this.isPropagationStopped = false;
+ };
+
+ lm.utils.BubblingEvent.prototype.stopPropagation = function () {
+ this.isPropagationStopped = true;
+ };
+ /**
+ * Minifies and unminifies configs by replacing frequent keys
+ * and values with one letter substitutes. Config options must
+ * retain array position/index, add new options at the end.
+ *
+ * @constructor
+ */
+ lm.utils.ConfigMinifier = function () {
+ this._keys = [
+ 'settings',
+ 'hasHeaders',
+ 'constrainDragToContainer',
+ 'selectionEnabled',
+ 'dimensions',
+ 'borderWidth',
+ 'minItemHeight',
+ 'minItemWidth',
+ 'headerHeight',
+ 'dragProxyWidth',
+ 'dragProxyHeight',
+ 'labels',
+ 'close',
+ 'maximise',
+ 'minimise',
+ 'popout',
+ 'content',
+ 'componentName',
+ 'componentState',
+ 'id',
+ 'width',
+ 'type',
+ 'height',
+ 'isClosable',
+ 'title',
+ 'popoutWholeStack',
+ 'openPopouts',
+ 'parentId',
+ 'activeItemIndex',
+ 'reorderEnabled',
+ 'borderGrabWidth',
+
+
+
+
+ //Maximum 36 entries, do not cross this line!
+ ];
+ if (this._keys.length > 36) {
+ throw new Error('Too many keys in config minifier map');
+ }
+
+ this._values = [
+ true,
+ false,
+ 'row',
+ 'column',
+ 'stack',
+ 'component',
+ 'close',
+ 'maximise',
+ 'minimise',
+ 'open in new window'
+ ];
+ };
+
+ lm.utils.copy(lm.utils.ConfigMinifier.prototype, {
+
+ /**
+ * Takes a GoldenLayout configuration object and
+ * replaces its keys and values recursively with
+ * one letter counterparts
+ *
+ * @param {Object} config A GoldenLayout config object
+ *
+ * @returns {Object} minified config
+ */
+ minifyConfig: function (config) {
+ var min = {};
+ this._nextLevel(config, min, '_min');
+ return min;
+ },
+
+ /**
+ * Takes a configuration Object that was previously minified
+ * using minifyConfig and returns its original version
+ *
+ * @param {Object} minifiedConfig
+ *
+ * @returns {Object} the original configuration
+ */
+ unminifyConfig: function (minifiedConfig) {
+ var orig = {};
+ this._nextLevel(minifiedConfig, orig, '_max');
+ return orig;
+ },
+
+ /**
+ * Recursive function, called for every level of the config structure
+ *
+ * @param {Array|Object} orig
+ * @param {Array|Object} min
+ * @param {String} translationFn
+ *
+ * @returns {void}
+ */
+ _nextLevel: function (from, to, translationFn) {
+ var key, minKey;
+
+ for (key in from) {
+
+ /**
+ * For in returns array indices as keys, so let's cast them to numbers
+ */
+ if (from instanceof Array) key = parseInt(key, 10);
+
+ /**
+ * In case something has extended Object prototypes
+ */
+ if (!from.hasOwnProperty(key)) continue;
+
+ /**
+ * Translate the key to a one letter substitute
+ */
+ minKey = this[translationFn](key, this._keys);
+
+ /**
+ * For Arrays and Objects, create a new Array/Object
+ * on the minified object and recurse into it
+ */
+ if (typeof from[key] === 'object') {
+ to[minKey] = from[key] instanceof Array ? [] : {};
+ this._nextLevel(from[key], to[minKey], translationFn);
+
+ /**
+ * For primitive values (Strings, Numbers, Boolean etc.)
+ * minify the value
+ */
+ } else {
+ to[minKey] = this[translationFn](from[key], this._values);
+ }
+ }
+ },
+
+ /**
+ * Minifies value based on a dictionary
+ *
+ * @param {String|Boolean} value
+ * @param {Array<String|Boolean>} dictionary
+ *
+ * @returns {String} The minified version
+ */
+ _min: function (value, dictionary) {
+ /**
+ * If a value actually is a single character, prefix it
+ * with ___ to avoid mistaking it for a minification code
+ */
+ if (typeof value === 'string' && value.length === 1) {
+ return '___' + value;
+ }
+
+ var index = lm.utils.indexOf(value, dictionary);
+
+ /**
+ * value not found in the dictionary, return it unmodified
+ */
+ if (index === -1) {
+ return value;
+
+ /**
+ * value found in dictionary, return its base36 counterpart
+ */
+ } else {
+ return index.toString(36);
+ }
+ },
+
+ _max: function (value, dictionary) {
+ /**
+ * value is a single character. Assume that it's a translation
+ * and return the original value from the dictionary
+ */
+ if (typeof value === 'string' && value.length === 1) {
+ return dictionary[parseInt(value, 36)];
+ }
+
+ /**
+ * value originally was a single character and was prefixed with ___
+ * to avoid mistaking it for a translation. Remove the prefix
+ * and return the original character
+ */
+ if (typeof value === 'string' && value.substr(0, 3) === '___') {
+ return value[3];
+ }
+ /**
+ * value was not minified
+ */
+ return value;
+ }
+ });
+
+ /**
+ * An EventEmitter singleton that propagates events
+ * across multiple windows. This is a little bit trickier since
+ * windows are allowed to open childWindows in their own right
+ *
+ * This means that we deal with a tree of windows. Hence the rules for event propagation are:
+ *
+ * - Propagate events from this layout to both parents and children
+ * - Propagate events from parent to this and children
+ * - Propagate events from children to the other children (but not the emitting one) and the parent
+ *
+ * @constructor
+ *
+ * @param {lm.LayoutManager} layoutManager
+ */
+ lm.utils.EventHub = function (layoutManager) {
+ lm.utils.EventEmitter.call(this);
+ this._layoutManager = layoutManager;
+ this._dontPropagateToParent = null;
+ this._childEventSource = null;
+ this.on(lm.utils.EventEmitter.ALL_EVENT, lm.utils.fnBind(this._onEventFromThis, this));
+ this._boundOnEventFromChild = lm.utils.fnBind(this._onEventFromChild, this);
+ $(window).on('gl_child_event', this._boundOnEventFromChild);
+ };
+
+ /**
+ * Called on every event emitted on this eventHub, regardles of origin.
+ *
+ * @private
+ *
+ * @param {Mixed}
+ *
+ * @returns {void}
+ */
+ lm.utils.EventHub.prototype._onEventFromThis = function () {
+ var args = Array.prototype.slice.call(arguments);
+
+ if (this._layoutManager.isSubWindow && args[0] !== this._dontPropagateToParent) {
+ this._propagateToParent(args);
+ }
+ this._propagateToChildren(args);
+
+ //Reset
+ this._dontPropagateToParent = null;
+ this._childEventSource = null;
+ };
+
+ /**
+ * Called by the parent layout.
+ *
+ * @param {Array} args Event name + arguments
+ *
+ * @returns {void}
+ */
+ lm.utils.EventHub.prototype._$onEventFromParent = function (args) {
+ this._dontPropagateToParent = args[0];
+ this.emit.apply(this, args);
+ };
+
+ /**
+ * Callback for child events raised on the window
+ *
+ * @param {DOMEvent} event
+ * @private
+ *
+ * @returns {void}
+ */
+ lm.utils.EventHub.prototype._onEventFromChild = function (event) {
+ this._childEventSource = event.originalEvent.__gl;
+ this.emit.apply(this, event.originalEvent.__glArgs);
+ };
+
+ /**
+ * Propagates the event to the parent by emitting
+ * it on the parent's DOM window
+ *
+ * @param {Array} args Event name + arguments
+ * @private
+ *
+ * @returns {void}
+ */
+ lm.utils.EventHub.prototype._propagateToParent = function (args) {
+ var event,
+ eventName = 'gl_child_event';
+
+ if (document.createEvent) {
+ event = window.opener.document.createEvent('HTMLEvents');
+ event.initEvent(eventName, true, true);
+ } else {
+ event = window.opener.document.createEventObject();
+ event.eventType = eventName;
+ }
+
+ event.eventName = eventName;
+ event.__glArgs = args;
+ event.__gl = this._layoutManager;
+
+ if (document.createEvent) {
+ window.opener.dispatchEvent(event);
+ } else {
+ window.opener.fireEvent('on' + event.eventType, event);
+ }
+ };
+
+ /**
+ * Propagate events to children
+ *
+ * @param {Array} args Event name + arguments
+ * @private
+ *
+ * @returns {void}
+ */
+ lm.utils.EventHub.prototype._propagateToChildren = function (args) {
+ var childGl, i;
+
+ for (i = 0; i < this._layoutManager.openPopouts.length; i++) {
+ childGl = this._layoutManager.openPopouts[i].getGlInstance();
+
+ if (childGl && childGl !== this._childEventSource) {
+ childGl.eventHub._$onEventFromParent(args);
+ }
+ }
+ };
+
+
+ /**
+ * Destroys the EventHub
+ *
+ * @public
+ * @returns {void}
+ */
+
+ lm.utils.EventHub.prototype.destroy = function () {
+ $(window).off('gl_child_event', this._boundOnEventFromChild);
+ };
+ /**
+ * A specialised GoldenLayout component that binds GoldenLayout container
+ * lifecycle events to react components
+ *
+ * @constructor
+ *
+ * @param {lm.container.ItemContainer} container
+ * @param {Object} state state is not required for react components
+ */
+ lm.utils.ReactComponentHandler = function (container, state) {
+ this._reactComponent = null;
+ this._originalComponentWillUpdate = null;
+ this._container = container;
+ this._initialState = state;
+ this._reactClass = this._getReactClass();
+ this._container.on('open', this._render, this);
+ this._container.on('destroy', this._destroy, this);
+ };
+
+ lm.utils.copy(lm.utils.ReactComponentHandler.prototype, {
+
+ /**
+ * Creates the react class and component and hydrates it with
+ * the initial state - if one is present
+ *
+ * By default, react's getInitialState will be used
+ *
+ * @private
+ * @returns {void}
+ */
+ _render: function () {
+ this._reactComponent = ReactDOM.render(this._getReactComponent(), this._container.getElement()[0]);
+ this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function () {
+ };
+ this._reactComponent.componentWillUpdate = this._onUpdate.bind(this);
+ if (this._container.getState()) {
+ this._reactComponent.setState(this._container.getState());
+ }
+ },
+
+ /**
+ * Removes the component from the DOM and thus invokes React's unmount lifecycle
+ *
+ * @private
+ * @returns {void}
+ */
+ _destroy: function () {
+ ReactDOM.unmountComponentAtNode(this._container.getElement()[0]);
+ this._container.off('open', this._render, this);
+ this._container.off('destroy', this._destroy, this);
+ },
+
+ /**
+ * Hooks into React's state management and applies the componentstate
+ * to GoldenLayout
+ *
+ * @private
+ * @returns {void}
+ */
+ _onUpdate: function (nextProps, nextState) {
+ this._container.setState(nextState);
+ this._originalComponentWillUpdate.call(this._reactComponent, nextProps, nextState);
+ },
+
+ /**
+ * Retrieves the react class from GoldenLayout's registry
+ *
+ * @private
+ * @returns {React.Class}
+ */
+ _getReactClass: function () {
+ var componentName = this._container._config.component;
+ var reactClass;
+
+ if (!componentName) {
+ throw new Error('No react component name. type: react-component needs a field `component`');
+ }
+
+ reactClass = this._container.layoutManager.getComponent(componentName);
+
+ if (!reactClass) {
+ throw new Error('React component "' + componentName + '" not found. ' +
+ 'Please register all components with GoldenLayout using `registerComponent(name, component)`');
+ }
+
+ return reactClass;
+ },
+
+ /**
+ * Copies and extends the properties array and returns the React element
+ *
+ * @private
+ * @returns {React.Element}
+ */
+ _getReactComponent: function () {
+ var defaultProps = {
+ glEventHub: this._container.layoutManager.eventHub,
+ glContainer: this._container,
+ };
+ var props = $.extend(defaultProps, this._container._config.props);
+ return React.createElement(this._reactClass, props);
+ }
+ });
+})(window.$); \ No newline at end of file
diff --git a/src/client/northstar/core/BaseObject.ts b/src/client/northstar/core/BaseObject.ts
new file mode 100644
index 000000000..ed3818071
--- /dev/null
+++ b/src/client/northstar/core/BaseObject.ts
@@ -0,0 +1,12 @@
+import { IEquatable } from '../utils/IEquatable';
+import { IDisposable } from '../utils/IDisposable';
+
+export class BaseObject implements IEquatable, IDisposable {
+
+ public Equals(other: Object): boolean {
+ return this === other;
+ }
+
+ public Dispose(): void {
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/core/attribute/AttributeModel.ts b/src/client/northstar/core/attribute/AttributeModel.ts
new file mode 100644
index 000000000..c89b1617c
--- /dev/null
+++ b/src/client/northstar/core/attribute/AttributeModel.ts
@@ -0,0 +1,111 @@
+import { Attribute, DataType, VisualizationHint } from '../../model/idea/idea';
+import { BaseObject } from '../BaseObject';
+import { observable } from "mobx";
+
+export abstract class AttributeModel extends BaseObject {
+ public abstract get DisplayName(): string;
+ public abstract get CodeName(): string;
+ public abstract get DataType(): DataType;
+ public abstract get VisualizationHints(): VisualizationHint[];
+}
+
+export class ColumnAttributeModel extends AttributeModel {
+ public Attribute: Attribute;
+
+ constructor(attribute: Attribute) {
+ super();
+ this.Attribute = attribute;
+ }
+
+ public get DataType(): DataType {
+ return this.Attribute.dataType ? this.Attribute.dataType : DataType.Undefined;
+ }
+
+ public get DisplayName(): string {
+ return this.Attribute.displayName ? this.Attribute.displayName.ReplaceAll("_", " ") : "";
+ }
+
+ public get CodeName(): string {
+ return this.Attribute.rawName ? this.Attribute.rawName : "";
+ }
+
+ public get VisualizationHints(): VisualizationHint[] {
+ return this.Attribute.visualizationHints ? this.Attribute.visualizationHints : [];
+ }
+
+ public Equals(other: ColumnAttributeModel): boolean {
+ return this.Attribute.rawName === other.Attribute.rawName;
+ }
+}
+
+export class CodeAttributeModel extends AttributeModel {
+ private _visualizationHints: VisualizationHint[];
+
+ public CodeName: string;
+
+ @observable
+ public Code: string;
+
+ constructor(code: string, codeName: string, displayName: string, visualizationHints: VisualizationHint[]) {
+ super();
+ this.Code = code;
+ this.CodeName = codeName;
+ this.DisplayName = displayName;
+ this._visualizationHints = visualizationHints;
+ }
+
+ public get DataType(): DataType {
+ return DataType.Undefined;
+ }
+
+ @observable
+ public DisplayName: string;
+
+ public get VisualizationHints(): VisualizationHint[] {
+ return this._visualizationHints;
+ }
+
+ public Equals(other: CodeAttributeModel): boolean {
+ return this.CodeName === other.CodeName;
+ }
+
+}
+
+export class BackendAttributeModel extends AttributeModel {
+ private _dataType: DataType;
+ private _displayName: string;
+ private _codeName: string;
+ private _visualizationHints: VisualizationHint[];
+
+ public Id: string;
+
+ constructor(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]) {
+ super();
+ this.Id = id;
+ this._dataType = dataType;
+ this._displayName = displayName;
+ this._codeName = codeName;
+ this._visualizationHints = visualizationHints;
+ }
+
+ public get DataType(): DataType {
+ return this._dataType;
+ }
+
+ public get DisplayName(): string {
+ return this._displayName.ReplaceAll("_", " ");
+ }
+
+ public get CodeName(): string {
+ return this._codeName;
+ }
+
+ public get VisualizationHints(): VisualizationHint[] {
+ return this._visualizationHints;
+ }
+
+ public Equals(other: BackendAttributeModel): boolean {
+ return this.Id === other.Id;
+ }
+
+} \ No newline at end of file
diff --git a/src/client/northstar/core/attribute/AttributeTransformationModel.ts b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
new file mode 100644
index 000000000..66485183b
--- /dev/null
+++ b/src/client/northstar/core/attribute/AttributeTransformationModel.ts
@@ -0,0 +1,52 @@
+
+import { computed, observable } from "mobx";
+import { AggregateFunction } from "../../model/idea/idea";
+import { AttributeModel } from "./AttributeModel";
+import { IEquatable } from "../../utils/IEquatable";
+
+export class AttributeTransformationModel implements IEquatable {
+
+ @observable public AggregateFunction: AggregateFunction;
+ @observable public AttributeModel: AttributeModel;
+
+ constructor(attributeModel: AttributeModel, aggregateFunction: AggregateFunction = AggregateFunction.None) {
+ this.AttributeModel = attributeModel;
+ this.AggregateFunction = aggregateFunction;
+ }
+
+ @computed
+ public get PresentedName(): string {
+ var displayName = this.AttributeModel.DisplayName;
+ if (this.AggregateFunction === AggregateFunction.Count) {
+ return "count";
+ }
+ if (this.AggregateFunction === AggregateFunction.Avg) {
+ displayName = "avg(" + displayName + ")";
+ }
+ else if (this.AggregateFunction === AggregateFunction.Max) {
+ displayName = "max(" + displayName + ")";
+ }
+ else if (this.AggregateFunction === AggregateFunction.Min) {
+ displayName = "min(" + displayName + ")";
+ }
+ else if (this.AggregateFunction === AggregateFunction.Sum) {
+ displayName = "sum(" + displayName + ")";
+ }
+ else if (this.AggregateFunction === AggregateFunction.SumE) {
+ displayName = "sumE(" + displayName + ")";
+ }
+
+ return displayName;
+ }
+
+ public clone(): AttributeTransformationModel {
+ var clone = new AttributeTransformationModel(this.AttributeModel);
+ clone.AggregateFunction = this.AggregateFunction;
+ return clone;
+ }
+
+ public Equals(other: AttributeTransformationModel): boolean {
+ return this.AggregateFunction === other.AggregateFunction &&
+ this.AttributeModel.Equals(other.AttributeModel);
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/core/attribute/CalculatedAttributeModel.ts b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
new file mode 100644
index 000000000..a197c1305
--- /dev/null
+++ b/src/client/northstar/core/attribute/CalculatedAttributeModel.ts
@@ -0,0 +1,42 @@
+import { BackendAttributeModel, AttributeModel, CodeAttributeModel } from "./AttributeModel";
+import { DataType, VisualizationHint } from '../../model/idea/idea';
+
+export class CalculatedAttributeManager {
+ public static AllCalculatedAttributes: Array<AttributeModel> = new Array<AttributeModel>();
+
+ public static Clear() {
+ this.AllCalculatedAttributes = new Array<AttributeModel>();
+ }
+
+ public static CreateBackendAttributeModel(id: string, dataType: DataType, displayName: string, codeName: string, visualizationHints: VisualizationHint[]): BackendAttributeModel {
+ var filtered = this.AllCalculatedAttributes.filter(am => {
+ if (am instanceof BackendAttributeModel &&
+ am.Id === id) {
+ return true;
+ }
+ return false;
+ });
+ if (filtered.length > 0) {
+ return filtered[0] as BackendAttributeModel;
+ }
+ var newAttr = new BackendAttributeModel(id, dataType, displayName, codeName, visualizationHints);
+ this.AllCalculatedAttributes.push(newAttr);
+ return newAttr;
+ }
+
+ public static CreateCodeAttributeModel(code: string, codeName: string, visualizationHints: VisualizationHint[]): CodeAttributeModel {
+ var filtered = this.AllCalculatedAttributes.filter(am => {
+ if (am instanceof CodeAttributeModel &&
+ am.CodeName === codeName) {
+ return true;
+ }
+ return false;
+ });
+ if (filtered.length > 0) {
+ return filtered[0] as CodeAttributeModel;
+ }
+ var newAttr = new CodeAttributeModel(code, codeName, codeName.ReplaceAll("_", " "), visualizationHints);
+ this.AllCalculatedAttributes.push(newAttr);
+ return newAttr;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/core/brusher/IBaseBrushable.ts b/src/client/northstar/core/brusher/IBaseBrushable.ts
new file mode 100644
index 000000000..87f4ba413
--- /dev/null
+++ b/src/client/northstar/core/brusher/IBaseBrushable.ts
@@ -0,0 +1,13 @@
+import { PIXIPoint } from '../../utils/MathUtil';
+import { IEquatable } from '../../utils/IEquatable';
+import { Doc } from '../../../../new_fields/Doc';
+
+export interface IBaseBrushable<T> extends IEquatable {
+ BrusherModels: Array<Doc>;
+ BrushColors: Array<number>;
+ Position: PIXIPoint;
+ Size: PIXIPoint;
+}
+export function instanceOfIBaseBrushable<T>(object: any): object is IBaseBrushable<T> {
+ return 'BrusherModels' in object;
+} \ No newline at end of file
diff --git a/src/client/northstar/core/brusher/IBaseBrusher.ts b/src/client/northstar/core/brusher/IBaseBrusher.ts
new file mode 100644
index 000000000..d2de6ed62
--- /dev/null
+++ b/src/client/northstar/core/brusher/IBaseBrusher.ts
@@ -0,0 +1,11 @@
+import { PIXIPoint } from '../../utils/MathUtil';
+import { IEquatable } from '../../utils/IEquatable';
+
+
+export interface IBaseBrusher<T> extends IEquatable {
+ Position: PIXIPoint;
+ Size: PIXIPoint;
+}
+export function instanceOfIBaseBrusher<T>(object: any): object is IBaseBrusher<T> {
+ return 'BrushableModels' in object;
+} \ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterModel.ts b/src/client/northstar/core/filter/FilterModel.ts
new file mode 100644
index 000000000..6ab96b33d
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterModel.ts
@@ -0,0 +1,83 @@
+import { ValueComparison } from "./ValueComparision";
+import { Utils } from "../../utils/Utils";
+import { IBaseFilterProvider } from "./IBaseFilterProvider";
+import { FilterOperand } from "./FilterOperand";
+import { HistogramField } from "../../dash-fields/HistogramField";
+import { Cast, FieldValue } from "../../../../new_fields/Types";
+import { Doc } from "../../../../new_fields/Doc";
+
+export class FilterModel {
+ public ValueComparisons: ValueComparison[];
+ constructor() {
+ this.ValueComparisons = new Array<ValueComparison>();
+ }
+
+ public Equals(other: FilterModel): boolean {
+ if (!Utils.EqualityHelper(this, other)) return false;
+ if (!this.isSame(this.ValueComparisons, (other).ValueComparisons)) return false;
+ return true;
+ }
+
+ private isSame(a: ValueComparison[], b: ValueComparison[]): boolean {
+ if (a.length !== b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ let valueComp = a[i];
+ if (!valueComp.Equals(b[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public ToPythonString(): string {
+ return "(" + this.ValueComparisons.map(vc => vc.ToPythonString()).join("&&") + ")";
+ }
+
+ public static And(filters: string[]): string {
+ let ret = filters.filter(f => f !== "").join(" && ");
+ return ret;
+ }
+ public static GetFilterModelsRecursive(baseOperation: IBaseFilterProvider, visitedFilterProviders: Set<IBaseFilterProvider>, filterModels: FilterModel[], isFirst: boolean): string {
+ let ret = "";
+ visitedFilterProviders.add(baseOperation);
+ let filtered = baseOperation.FilterModels.filter(fm => fm && fm.ValueComparisons.length > 0);
+ if (!isFirst && filtered.length > 0) {
+ filterModels.push(...filtered);
+ ret = "(" + baseOperation.FilterModels.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+ }
+ if (Utils.isBaseFilterConsumer(baseOperation) && baseOperation.Links) {
+ let children = new Array<string>();
+ let linkedGraphNodes = baseOperation.Links;
+ linkedGraphNodes.map(linkVm => {
+ let filterDoc = FieldValue(Cast(linkVm.linkedFrom, Doc));
+ if (filterDoc) {
+ let filterHistogram = Cast(filterDoc.data, HistogramField);
+ if (filterHistogram) {
+ if (!visitedFilterProviders.has(filterHistogram.HistoOp)) {
+ let child = FilterModel.GetFilterModelsRecursive(filterHistogram.HistoOp, visitedFilterProviders, filterModels, false);
+ if (child !== "") {
+ // if (linkVm.IsInverted) {
+ // child = "! " + child;
+ // }
+ children.push(child);
+ }
+ }
+ }
+ }
+ });
+
+ let childrenJoined = children.join(baseOperation.FilterOperand === FilterOperand.AND ? " && " : " || ");
+ if (children.length > 0) {
+ if (ret !== "") {
+ ret = "(" + ret + " && (" + childrenJoined + "))";
+ }
+ else {
+ ret = "(" + childrenJoined + ")";
+ }
+ }
+ }
+ return ret;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterOperand.ts b/src/client/northstar/core/filter/FilterOperand.ts
new file mode 100644
index 000000000..2e8e8d6a0
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterOperand.ts
@@ -0,0 +1,5 @@
+export enum FilterOperand
+{
+ AND,
+ OR
+} \ No newline at end of file
diff --git a/src/client/northstar/core/filter/FilterType.ts b/src/client/northstar/core/filter/FilterType.ts
new file mode 100644
index 000000000..9adbc087f
--- /dev/null
+++ b/src/client/northstar/core/filter/FilterType.ts
@@ -0,0 +1,6 @@
+export enum FilterType
+{
+ Filter,
+ Brush,
+ Slice
+} \ No newline at end of file
diff --git a/src/client/northstar/core/filter/IBaseFilterConsumer.ts b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
new file mode 100644
index 000000000..e7549d113
--- /dev/null
+++ b/src/client/northstar/core/filter/IBaseFilterConsumer.ts
@@ -0,0 +1,12 @@
+import { FilterOperand } from '../filter/FilterOperand';
+import { IEquatable } from '../../utils/IEquatable';
+import { Doc } from '../../../../new_fields/Doc';
+
+export interface IBaseFilterConsumer extends IEquatable {
+ FilterOperand: FilterOperand;
+ Links: Doc[];
+}
+
+export function instanceOfIBaseFilterConsumer(object: any): object is IBaseFilterConsumer {
+ return 'FilterOperand' in object;
+} \ No newline at end of file
diff --git a/src/client/northstar/core/filter/IBaseFilterProvider.ts b/src/client/northstar/core/filter/IBaseFilterProvider.ts
new file mode 100644
index 000000000..fc3301b11
--- /dev/null
+++ b/src/client/northstar/core/filter/IBaseFilterProvider.ts
@@ -0,0 +1,8 @@
+import { FilterModel } from '../filter/FilterModel';
+
+export interface IBaseFilterProvider {
+ FilterModels: Array<FilterModel>;
+}
+export function instanceOfIBaseFilterProvider(object: any): object is IBaseFilterProvider {
+ return 'FilterModels' in object;
+} \ No newline at end of file
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
new file mode 100644
index 000000000..65687a82b
--- /dev/null
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -0,0 +1,78 @@
+import { Predicate } from '../../model/idea/idea';
+import { Utils } from '../../utils/Utils';
+import { AttributeModel } from '../attribute/AttributeModel';
+
+export class ValueComparison {
+
+ public attributeModel: AttributeModel;
+ public Value: any;
+ public Predicate: Predicate;
+
+ public constructor(attributeModel: AttributeModel, predicate: Predicate, value: any) {
+ this.attributeModel = attributeModel;
+ this.Value = value;
+ this.Predicate = predicate;
+ }
+
+ public Equals(other: Object): boolean {
+ if (!Utils.EqualityHelper(this, other)) {
+ return false;
+ }
+ if (this.Predicate !== (other as ValueComparison).Predicate) {
+ return false;
+ }
+ let isComplex = (typeof this.Value === "object");
+ if (!isComplex && this.Value !== (other as ValueComparison).Value) {
+ return false;
+ }
+ if (isComplex && !this.Value.Equals((other as ValueComparison).Value)) {
+ return false;
+ }
+ return true;
+ }
+
+ public ToPythonString(): string {
+ var op = "";
+ switch (this.Predicate) {
+ case Predicate.EQUALS:
+ op = "==";
+ break;
+ case Predicate.GREATER_THAN:
+ op = ">";
+ break;
+ case Predicate.GREATER_THAN_EQUAL:
+ op = ">=";
+ break;
+ case Predicate.LESS_THAN:
+ op = "<";
+ break;
+ case Predicate.LESS_THAN_EQUAL:
+ op = "<=";
+ break;
+ default:
+ op = "==";
+ break;
+ }
+
+ var val = this.Value.toString();
+ if (typeof this.Value === 'string' || this.Value instanceof String) {
+ val = "\"" + val + "\"";
+ }
+ var ret = " ";
+ var rawName = this.attributeModel.CodeName;
+ switch (this.Predicate) {
+ case Predicate.STARTS_WITH:
+ ret += rawName + " != null && " + rawName + ".StartsWith(" + val + ") ";
+ return ret;
+ case Predicate.ENDS_WITH:
+ ret += rawName + " != null && " + rawName + ".EndsWith(" + val + ") ";
+ return ret;
+ case Predicate.CONTAINS:
+ ret += rawName + " != null && " + rawName + ".Contains(" + val + ") ";
+ return ret;
+ default:
+ ret += rawName + " " + op + " " + val + " ";
+ return ret;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts
new file mode 100644
index 000000000..1ee2189b9
--- /dev/null
+++ b/src/client/northstar/dash-fields/HistogramField.ts
@@ -0,0 +1,58 @@
+import { observable } from "mobx";
+import { custom, serializable } from "serializr";
+import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
+import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
+import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
+import { ObjectField, Copy } from "../../../new_fields/ObjectField";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { OmitKeys } from "../../../Utils";
+import { Deserializable } from "../../util/SerializationHelper";
+
+function serialize(field: HistogramField) {
+ let obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit;
+ return obj;
+}
+
+function deserialize(jp: any) {
+ let X: AttributeTransformationModel | undefined;
+ let Y: AttributeTransformationModel | undefined;
+ let V: AttributeTransformationModel | undefined;
+
+ let schema = CurrentUserUtils.GetNorthstarSchema(jp.SchemaName);
+ if (schema) {
+ CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => {
+ if (attr.displayName === jp.X.AttributeModel.Attribute.DisplayName) {
+ X = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.X.AggregateFunction);
+ }
+ if (attr.displayName === jp.Y.AttributeModel.Attribute.DisplayName) {
+ Y = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.Y.AggregateFunction);
+ }
+ if (attr.displayName === jp.V.AttributeModel.Attribute.DisplayName) {
+ V = new AttributeTransformationModel(new ColumnAttributeModel(attr), jp.V.AggregateFunction);
+ }
+ });
+ if (X && Y && V) {
+ return new HistogramOperation(jp.SchemaName, X, Y, V, jp.Normalization);
+ }
+ }
+ return HistogramOperation.Empty;
+}
+
+@Deserializable("histogramField")
+export class HistogramField extends ObjectField {
+ @serializable(custom(serialize, deserialize)) @observable public readonly HistoOp: HistogramOperation;
+ constructor(data?: HistogramOperation) {
+ super();
+ this.HistoOp = data ? data : HistogramOperation.Empty;
+ }
+
+ toString(): string {
+ return JSON.stringify(OmitKeys(this.HistoOp, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit);
+ }
+
+ [Copy]() {
+ let y = this.HistoOp;
+ let z = this.HistoOp.Copy;
+ return new HistogramField(HistogramOperation.Duplicate(this.HistoOp));
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
new file mode 100644
index 000000000..3e9145a1b
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBinPrimitiveCollection.ts
@@ -0,0 +1,240 @@
+import React = require("react");
+import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
+import { ChartType } from '../../northstar/model/binRanges/VisualBinRange';
+import { AggregateFunction, Bin, Brush, DoubleValueAggregateResult, HistogramResult, MarginAggregateParameters, MarginAggregateResult } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+import { LABColor } from '../../northstar/utils/LABcolor';
+import { PIXIRectangle } from "../../northstar/utils/MathUtil";
+import { StyleConstants } from "../../northstar/utils/StyleContants";
+import { HistogramBox } from "./HistogramBox";
+import "./HistogramBoxPrimitives.scss";
+
+export class HistogramBinPrimitive {
+ constructor(init?: Partial<HistogramBinPrimitive>) {
+ Object.assign(this, init);
+ }
+ public DataValue: number = 0;
+ public Rect: PIXIRectangle = PIXIRectangle.EMPTY;
+ public MarginRect: PIXIRectangle = PIXIRectangle.EMPTY;
+ public MarginPercentage: number = 0;
+ public Color: number = StyleConstants.WARNING_COLOR;
+ public Opacity: number = 1;
+ public BrushIndex: number = 0;
+ public BarAxis: number = -1;
+}
+
+export class HistogramBinPrimitiveCollection {
+ private static TOLERANCE: number = 0.0001;
+
+ private _histoBox: HistogramBox;
+ private get histoOp() { return this._histoBox.HistoOp; }
+ private get histoResult() { return this.histoOp.Result as HistogramResult; }
+ private get sizeConverter() { return this._histoBox.SizeConverter; }
+ public BinPrimitives: Array<HistogramBinPrimitive> = new Array<HistogramBinPrimitive>();
+ public HitGeom: PIXIRectangle = PIXIRectangle.EMPTY;
+
+ constructor(bin: Bin, histoBox: HistogramBox) {
+ this._histoBox = histoBox;
+ let brushing = this.setupBrushing(bin, this.histoOp.Normalization); // X= 0, Y = 1, V = 2
+
+ brushing.orderedBrushes.reduce((brushFactorSum, brush) => {
+ switch (histoBox.ChartType) {
+ case ChartType.VerticalBar: return this.createVerticalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
+ case ChartType.HorizontalBar: return this.createHorizontalBarChartBinPrimitives(bin, brush, brushing.maxAxis, this.histoOp.Normalization);
+ case ChartType.SinglePoint: return this.createSinglePointChartBinPrimitives(bin, brush);
+ case ChartType.HeatMap: return this.createHeatmapBinPrimitives(bin, brush, brushFactorSum);
+ }
+ }, 0);
+
+ // adjust brush rects (stacking or not)
+ var allBrushIndex = ModelHelpers.AllBrushIndex(this.histoResult);
+ var filteredBinPrims = this.BinPrimitives.filter(b => b.BrushIndex !== allBrushIndex && b.DataValue !== 0.0);
+ filteredBinPrims.reduce((sum, fbp) => {
+ if (histoBox.ChartType === ChartType.VerticalBar) {
+ if (this.histoOp.Y.AggregateFunction === AggregateFunction.Count) {
+ fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y - sum, fbp.Rect.width, fbp.Rect.height);
+ fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - sum, fbp.MarginRect.width, fbp.MarginRect.height);
+ return sum + fbp.Rect.height;
+ }
+ if (this.histoOp.Y.AggregateFunction === AggregateFunction.Avg) {
+ var w = fbp.Rect.width / 2.0;
+ fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width / filteredBinPrims.length, fbp.Rect.height);
+ fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x - w + sum + (fbp.Rect.width / 2.0), fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
+ return sum + fbp.Rect.width;
+ }
+ }
+ else if (histoBox.ChartType === ChartType.HorizontalBar) {
+ if (this.histoOp.X.AggregateFunction === AggregateFunction.Count) {
+ fbp.Rect = new PIXIRectangle(fbp.Rect.x + sum, fbp.Rect.y, fbp.Rect.width, fbp.Rect.height);
+ fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x + sum, fbp.MarginRect.y, fbp.MarginRect.width, fbp.MarginRect.height);
+ return sum + fbp.Rect.width;
+ }
+ if (this.histoOp.X.AggregateFunction === AggregateFunction.Avg) {
+ var h = fbp.Rect.height / 2.0;
+ fbp.Rect = new PIXIRectangle(fbp.Rect.x, fbp.Rect.y + sum, fbp.Rect.width, fbp.Rect.height / filteredBinPrims.length);
+ fbp.MarginRect = new PIXIRectangle(fbp.MarginRect.x, fbp.MarginRect.y - h + sum + (fbp.Rect.height / 2.0), fbp.MarginRect.width, fbp.MarginRect.height);
+ return sum + fbp.Rect.height;
+ }
+ }
+ return 0;
+ }, 0);
+ this.BinPrimitives = this.BinPrimitives.reverse();
+ var f = this.BinPrimitives.filter(b => b.BrushIndex === allBrushIndex);
+ this.HitGeom = f.length > 0 ? f[0].Rect : PIXIRectangle.EMPTY;
+ }
+
+ private setupBrushing(bin: Bin, normalization: number) {
+ var overlapBrushIndex = ModelHelpers.OverlapBrushIndex(this.histoResult);
+ var orderedBrushes = [this.histoResult.brushes![0], this.histoResult.brushes![overlapBrushIndex]];
+ this.histoResult.brushes!.map(brush => brush.brushIndex !== 0 && brush.brushIndex !== overlapBrushIndex && orderedBrushes.push(brush));
+ return {
+ orderedBrushes,
+ maxAxis: orderedBrushes.reduce((prev, Brush) => {
+ let aggResult = this.getBinValue(normalization, bin, Brush.brushIndex!);
+ return aggResult !== undefined && aggResult > prev ? aggResult : prev;
+ }, Number.MIN_VALUE)
+ };
+ }
+
+ private createHeatmapBinPrimitives(bin: Bin, brush: Brush, brushFactorSum: number): number {
+
+ let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
+ if (unNormalizedValue === undefined) {
+ return brushFactorSum;
+ }
+
+ var normalizedValue = (unNormalizedValue - this._histoBox.ValueRange[0]) / (Math.abs((this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0])) < HistogramBinPrimitiveCollection.TOLERANCE ?
+ unNormalizedValue : this._histoBox.ValueRange[1] - this._histoBox.ValueRange[0]);
+
+ let allUnNormalizedValue = this.getBinValue(2, bin, ModelHelpers.AllBrushIndex(this.histoResult));
+
+ // bcz: are these calls needed?
+ let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
+ let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
+
+ var returnBrushFactorSum = brushFactorSum;
+ if (allUnNormalizedValue !== undefined) {
+ var brushFactor = (unNormalizedValue / allUnNormalizedValue);
+ returnBrushFactorSum += brushFactor;
+ returnBrushFactorSum = Math.min(returnBrushFactorSum, 1.0);
+
+ var tempRect = new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo);
+ var ratio = (tempRect.width / tempRect.height);
+ var newHeight = Math.sqrt((1.0 / ratio) * ((tempRect.width * tempRect.height) * returnBrushFactorSum));
+ var newWidth = newHeight * ratio;
+
+ xFrom = (tempRect.x + (tempRect.width - newWidth) / 2.0);
+ yTo = (tempRect.y + (tempRect.height - newHeight) / 2.0);
+ xTo = (xFrom + newWidth);
+ yFrom = (yTo + newHeight);
+ }
+ var alpha = 0.0;
+ var color = this.baseColorFromBrush(brush);
+ var lerpColor = LABColor.Lerp(
+ LABColor.FromColor(StyleConstants.MIN_VALUE_COLOR),
+ LABColor.FromColor(color),
+ (alpha + Math.pow(normalizedValue, 1.0 / 3.0) * (1.0 - alpha)));
+ var dataColor = LABColor.ToColor(lerpColor);
+
+ this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, dataColor, 1, unNormalizedValue);
+ return returnBrushFactorSum;
+ }
+
+ private createSinglePointChartBinPrimitives(bin: Bin, brush: Brush): number {
+ let unNormalizedValue = this.getBinValue(2, bin, brush.brushIndex!);
+ if (unNormalizedValue !== undefined) {
+ let [xFrom, xTo] = this.sizeConverter.DataToScreenPointRange(0, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.X, this.histoResult, brush.brushIndex!));
+ let [yFrom, yTo] = this.sizeConverter.DataToScreenPointRange(1, bin, ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, this.histoOp.Y, this.histoResult, brush.brushIndex!));
+
+ if (xFrom !== undefined && yFrom !== undefined && xTo !== undefined && yTo !== undefined) {
+ this.createBinPrimitive(-1, brush, PIXIRectangle.EMPTY, 0, xFrom, xTo, yFrom, yTo, this.baseColorFromBrush(brush), 1, unNormalizedValue);
+ }
+ }
+ return 0;
+ }
+
+ private createVerticalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
+ let dataValue = this.getBinValue(1, bin, brush.brushIndex!);
+ if (dataValue !== undefined) {
+ let [yFrom, yValue, yTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 1, binBrushMaxAxis);
+ let [xFrom, xTo] = this.sizeConverter.DataToScreenXAxisRange(this._histoBox.VisualBinRanges, 0, bin);
+
+ var yMarginAbsolute = this.getMargin(bin, brush, this.histoOp.Y);
+ var marginRect = new PIXIRectangle(xFrom + (xTo - xFrom) / 2.0 - 1,
+ this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute), 2,
+ this.sizeConverter.DataToScreenY(yValue - yMarginAbsolute) - this.sizeConverter.DataToScreenY(yValue + yMarginAbsolute));
+
+ this.createBinPrimitive(1, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+ this.baseColorFromBrush(brush), normalization !== 0 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[1] + 0.4, dataValue);
+ }
+ return 0;
+ }
+
+ private createHorizontalBarChartBinPrimitives(bin: Bin, brush: Brush, binBrushMaxAxis: number, normalization: number): number {
+ let dataValue = this.getBinValue(0, bin, brush.brushIndex!);
+ if (dataValue !== undefined) {
+ let [xFrom, xValue, xTo] = this.sizeConverter.DataToScreenNormalizedRange(dataValue, normalization, 0, binBrushMaxAxis);
+ let [yFrom, yTo] = this.sizeConverter.DataToScreenYAxisRange(this._histoBox.VisualBinRanges, 1, bin);
+
+ var xMarginAbsolute = this.sizeConverter.IsSmall ? 0 : this.getMargin(bin, brush, this.histoOp.X);
+ var marginRect = new PIXIRectangle(this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+ yTo + (yFrom - yTo) / 2.0 - 1,
+ this.sizeConverter.DataToScreenX(xValue + xMarginAbsolute) - this.sizeConverter.DataToScreenX(xValue - xMarginAbsolute),
+ 2.0);
+
+ this.createBinPrimitive(0, brush, marginRect, 0, xFrom, xTo, yFrom, yTo,
+ this.baseColorFromBrush(brush), normalization !== 1 ? 1 : 0.6 * binBrushMaxAxis / this.sizeConverter.DataRanges[0] + 0.4, dataValue);
+ }
+ return 0;
+ }
+
+ public getBinValue(axis: number, bin: Bin, brushIndex: number) {
+ var aggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis === 0 ? this.histoOp.X : axis === 1 ? this.histoOp.Y : this.histoOp.V, this.histoResult, brushIndex);
+ let dataValue = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
+ return dataValue !== null && dataValue.hasResult ? dataValue.result : undefined;
+ }
+
+ private getMargin(bin: Bin, brush: Brush, axis: AttributeTransformationModel) {
+ var marginParams = new MarginAggregateParameters();
+ marginParams.aggregateFunction = axis.AggregateFunction;
+ var marginAggregateKey = ModelHelpers.CreateAggregateKey(this.histoOp.Schema!.distinctAttributeParameters, axis, this.histoResult, brush.brushIndex!, marginParams);
+ let aggResult = ModelHelpers.GetAggregateResult(bin, marginAggregateKey);
+ return aggResult instanceof MarginAggregateResult && aggResult.absolutMargin ? aggResult.absolutMargin : 0;
+ }
+
+ private createBinPrimitive(barAxis: number, brush: Brush, marginRect: PIXIRectangle,
+ marginPercentage: number, xFrom: number, xTo: number, yFrom: number, yTo: number, color: number, opacity: number, dataValue: number) {
+ var binPrimitive = new HistogramBinPrimitive(
+ {
+ Rect: new PIXIRectangle(xFrom, yTo, xTo - xFrom, yFrom - yTo),
+ MarginRect: marginRect,
+ MarginPercentage: marginPercentage,
+ BrushIndex: brush.brushIndex,
+ Color: color,
+ Opacity: opacity,
+ DataValue: dataValue,
+ BarAxis: barAxis
+ });
+ this.BinPrimitives.push(binPrimitive);
+ }
+
+ private baseColorFromBrush(brush: Brush): number {
+ let bc = StyleConstants.BRUSH_COLORS;
+ if (brush.brushIndex === ModelHelpers.RestBrushIndex(this.histoResult)) {
+ return StyleConstants.HIGHLIGHT_COLOR;
+ }
+ else if (brush.brushIndex === ModelHelpers.OverlapBrushIndex(this.histoResult)) {
+ return StyleConstants.OVERLAP_COLOR;
+ }
+ else if (brush.brushIndex === ModelHelpers.AllBrushIndex(this.histoResult)) {
+ return 0x00ff00;
+ }
+ else if (bc.length > 0) {
+ return bc[brush.brushIndex! % bc.length];
+ }
+ // else if (this.histoOp.BrushColors.length > 0) {
+ // return this.histoOp.BrushColors[brush.brushIndex! % this.histoOp.BrushColors.length];
+ // }
+ return StyleConstants.HIGHLIGHT_COLOR;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
new file mode 100644
index 000000000..06d781263
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -0,0 +1,40 @@
+.histogrambox-container {
+ padding: 0vw;
+ position: absolute;
+ top: -50%;
+ left:-50%;
+ text-align: center;
+ width: 100%;
+ height: 100%;
+ background: black;
+ }
+ .histogrambox-xaxislabel {
+ position:absolute;
+ left:0;
+ width:100%;
+ text-align: center;
+ bottom:0;
+ background: lightgray;
+ font-size: 14;
+ font-weight: bold;
+ }
+ .histogrambox-yaxislabel {
+ position:absolute;
+ height:100%;
+ width: 25px;
+ left:0;
+ bottom:0;
+ background: lightgray;
+ }
+ .histogrambox-yaxislabel-text {
+ position:absolute;
+ left:0;
+ width: 1000px;
+ transform-origin: 10px 10px;
+ transform: rotate(-90deg);
+ text-align: left;
+ font-size: 14;
+ font-weight: bold;
+ bottom: calc(50% - 25px);
+ }
+ \ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
new file mode 100644
index 000000000..eb1ad69b7
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -0,0 +1,174 @@
+import React = require("react");
+import { action, computed, observable, reaction, runInAction, trace } from "mobx";
+import { observer } from "mobx-react";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
+import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
+import { AggregateBinRange, AggregateFunction, BinRange, Catalog, DoubleValueAggregateResult, HistogramResult } from "../../northstar/model/idea/idea";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
+import { SizeConverter } from "../../northstar/utils/SizeConverter";
+import { DragManager } from "../../util/DragManager";
+import { FieldView, FieldViewProps } from "../../views/nodes/FieldView";
+import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
+import { HistogramField } from "../dash-fields/HistogramField";
+import "../utils/Extensions";
+import "./HistogramBox.scss";
+import { HistogramBoxPrimitives } from './HistogramBoxPrimitives';
+import { HistogramLabelPrimitives } from "./HistogramLabelPrimitives";
+import { StyleConstants } from "../utils/StyleContants";
+import { Cast } from "../../../new_fields/Types";
+import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/RefField";
+
+
+@observer
+export class HistogramBox extends React.Component<FieldViewProps> {
+ public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(HistogramBox, fieldStr); }
+ private _dropXRef = React.createRef<HTMLDivElement>();
+ private _dropYRef = React.createRef<HTMLDivElement>();
+ private _dropXDisposer?: DragManager.DragDropDisposer;
+ private _dropYDisposer?: DragManager.DragDropDisposer;
+
+ @observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
+ @observable public VisualBinRanges: VisualBinRange[] = [];
+ @observable public ValueRange: number[] = [];
+ @computed public get HistogramResult(): HistogramResult { return this.HistoOp.Result as HistogramResult; }
+ @observable public SizeConverter: SizeConverter = new SizeConverter();
+
+ @computed get createOperationParamsCache() { trace(); return this.HistoOp.CreateOperationParameters(); }
+ @computed get BinRanges() { return this.HistogramResult ? this.HistogramResult.binRanges : undefined; }
+ @computed get ChartType() {
+ return !this.BinRanges ? ChartType.SinglePoint : this.BinRanges[0] instanceof AggregateBinRange ?
+ (this.BinRanges[1] instanceof AggregateBinRange ? ChartType.SinglePoint : ChartType.HorizontalBar) :
+ this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap;
+ }
+
+ @action
+ dropX = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ let h = Cast(de.data.draggedDocuments[0].data, HistogramField);
+ if (h) {
+ this.HistoOp.X = h.HistoOp.X;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+ @action
+ dropY = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ let h = Cast(de.data.draggedDocuments[0].data, HistogramField);
+ if (h) {
+ this.HistoOp.Y = h.HistoOp.X;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ @action
+ xLabelPointerDown = (e: React.PointerEvent) => {
+ this.HistoOp.X = new AttributeTransformationModel(this.HistoOp.X.AttributeModel, this.HistoOp.X.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
+ }
+ @action
+ yLabelPointerDown = (e: React.PointerEvent) => {
+ this.HistoOp.Y = new AttributeTransformationModel(this.HistoOp.Y.AttributeModel, this.HistoOp.Y.AggregateFunction === AggregateFunction.None ? AggregateFunction.Count : AggregateFunction.None);
+ }
+
+ componentDidMount() {
+ if (this._dropXRef.current) {
+ this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, { handlers: { drop: this.dropX.bind(this) } });
+ }
+ if (this._dropYRef.current) {
+ this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, { handlers: { drop: this.dropY.bind(this) } });
+ }
+ reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
+ reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
+ reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], (size: number[]) => this.SizeConverter.SetIsSmall(size[0] < 40 && size[1] < 40));
+ reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined,
+ (binRanges: BinRange[] | undefined) => {
+ if (binRanges) {
+ this.VisualBinRanges.splice(0, this.VisualBinRanges.length, ...binRanges.map((br, ind) =>
+ VisualBinRangeHelper.GetVisualBinRange(this.HistoOp.Schema!.distinctAttributeParameters, br, this.HistogramResult, ind ? this.HistoOp.Y : this.HistoOp.X, this.ChartType)));
+
+ let valueAggregateKey = ModelHelpers.CreateAggregateKey(this.HistoOp.Schema!.distinctAttributeParameters, this.HistoOp.V, this.HistogramResult, ModelHelpers.AllBrushIndex(this.HistogramResult));
+ this.ValueRange = Object.values(this.HistogramResult.bins!).reduce((prev, cur) => {
+ let value = ModelHelpers.GetAggregateResult(cur, valueAggregateKey) as DoubleValueAggregateResult;
+ return value && value.hasResult ? [Math.min(prev[0], value.result!), Math.max(prev[1], value.result!)] : prev;
+ }, [Number.MAX_VALUE, Number.MIN_VALUE]);
+ }
+ });
+ }
+
+ componentWillUnmount() {
+ if (this._dropXDisposer) {
+ this._dropXDisposer();
+ }
+ if (this._dropYDisposer) {
+ this._dropYDisposer();
+ }
+ }
+
+ async activateHistogramOperation(catalog?: Catalog) {
+ if (catalog) {
+ let histoOp = await Cast(this.props.Document[this.props.fieldKey], HistogramField);
+ runInAction(() => {
+ this.HistoOp = histoOp ? histoOp.HistoOp : HistogramOperation.Empty;
+ if (this.HistoOp !== HistogramOperation.Empty) {
+ reaction(() => DocListCast(this.props.Document.linkedFromDocs), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+ reaction(() => DocListCast(this.props.Document.brushingDocs).length,
+ async () => {
+ let brushingDocs = await DocListCastAsync(this.props.Document.brushingDocs);
+ const proto = this.props.Document.proto;
+ if (proto && brushingDocs) {
+ let mapped = brushingDocs.map((brush, i) => {
+ brush.backgroundColor = StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length];
+ let brushed = DocListCast(brush.brushingDocs);
+ return { l: brush, b: brushed[0][Id] === proto[Id] ? brushed[1] : brushed[0] };
+ });
+ this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...mapped);
+ }
+ }, { fireImmediately: true });
+ reaction(() => this.createOperationParamsCache, () => this.HistoOp.Update(), { fireImmediately: true });
+ }
+ });
+ }
+ }
+
+ @action
+ private onScrollWheel = (e: React.WheelEvent) => {
+ this.HistoOp.DrillDown(e.deltaY > 0);
+ e.stopPropagation();
+ }
+
+ render() {
+ let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
+ let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
+ let loff = this.SizeConverter.LeftOffset;
+ let toff = this.SizeConverter.TopOffset;
+ let roff = this.SizeConverter.RightOffset;
+ let boff = this.SizeConverter.BottomOffset;
+ return (
+ <div className="histogrambox-container" onWheel={this.onScrollWheel}>
+ <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
+ <span className="histogrambox-yaxislabel-text">
+ {labelY}
+ </span>
+ </div>
+ <div className="histogrambox-primitives" style={{
+ transform: `translate(${loff + 25}px, ${toff}px)`,
+ width: `calc(100% - ${loff + roff + 25}px)`,
+ height: `calc(100% - ${toff + boff}px)`,
+ }}>
+ <HistogramLabelPrimitives HistoBox={this} />
+ <HistogramBoxPrimitives HistoBox={this} />
+ </div>
+ <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >
+ {labelX}
+ </div>
+ </div>
+ );
+ }
+}
+
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
new file mode 100644
index 000000000..26203612a
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.scss
@@ -0,0 +1,42 @@
+.histogramboxprimitives-container {
+ width: 100%;
+ height: 100%;
+}
+.histogramboxprimitives-border {
+ border: 3px;
+ pointer-events: none;
+ position: absolute;
+ fill:"transparent";
+ stroke: white;
+ stroke-width: 1px;
+}
+.histogramboxprimitives-bar {
+ position: absolute;
+ border: 1px;
+ border-style: solid;
+ border-color: #282828;
+ pointer-events: all;
+}
+
+.histogramboxprimitives-placer {
+ position: absolute;
+ pointer-events: none;
+ width: 100%;
+ height: 100%;
+}
+.histogramboxprimitives-svgContainer {
+ position: absolute;
+ top:0;
+ left:0;
+ width:100%;
+ height: 100%;
+}
+.histogramboxprimitives-line {
+ position: absolute;
+ background: darkGray;
+ stroke: darkGray;
+ stroke-width: 1px;
+ width:100%;
+ height:100%;
+ opacity: 0.4;
+} \ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
new file mode 100644
index 000000000..350987695
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramBoxPrimitives.tsx
@@ -0,0 +1,124 @@
+import React = require("react");
+import { computed, observable, reaction, runInAction, trace, action } from "mobx";
+import { observer } from "mobx-react";
+import { Utils as DashUtils, emptyFunction } from '../../../Utils';
+import { FilterModel } from "../../northstar/core/filter/FilterModel";
+import { ModelHelpers } from "../../northstar/model/ModelHelpers";
+import { ArrayUtil } from "../../northstar/utils/ArrayUtil";
+import { LABColor } from '../../northstar/utils/LABcolor';
+import { PIXIRectangle } from "../../northstar/utils/MathUtil";
+import { StyleConstants } from "../../northstar/utils/StyleContants";
+import { HistogramBinPrimitiveCollection, HistogramBinPrimitive } from "./HistogramBinPrimitiveCollection";
+import { HistogramBox } from "./HistogramBox";
+import "./HistogramBoxPrimitives.scss";
+
+export interface HistogramPrimitivesProps {
+ HistoBox: HistogramBox;
+}
+@observer
+export class HistogramBoxPrimitives extends React.Component<HistogramPrimitivesProps> {
+ private get histoOp() { return this.props.HistoBox.HistoOp; }
+ private get renderDimension() { return this.props.HistoBox.SizeConverter.RenderDimension; }
+ @observable _selectedPrims: HistogramBinPrimitive[] = [];
+ @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
+ @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
+ @computed get selectedPrimitives() { return this._selectedPrims.map(bp => this.drawRect(bp.Rect, bp.BarAxis, undefined, "border")); }
+ @computed get barPrimitives() {
+ let histoResult = this.props.HistoBox.HistogramResult;
+ if (!histoResult || !histoResult.bins || !this.props.HistoBox.VisualBinRanges.length) {
+ return (null);
+ }
+ let allBrushIndex = ModelHelpers.AllBrushIndex(histoResult);
+ return Object.keys(histoResult.bins).reduce((prims: JSX.Element[], key: string) => {
+ let drawPrims = new HistogramBinPrimitiveCollection(histoResult.bins![key], this.props.HistoBox);
+ let toggle = this.getSelectionToggle(drawPrims.BinPrimitives, allBrushIndex,
+ ModelHelpers.GetBinFilterModel(histoResult.bins![key], allBrushIndex, histoResult, this.histoOp.X, this.histoOp.Y));
+ drawPrims.BinPrimitives.filter(bp => bp.DataValue && bp.BrushIndex !== allBrushIndex).map(bp =>
+ prims.push(...[{ r: bp.Rect, c: bp.Color }, { r: bp.MarginRect, c: StyleConstants.MARGIN_BARS_COLOR }].map(pair => this.drawRect(pair.r, bp.BarAxis, pair.c, "bar", toggle))));
+ return prims;
+ }, [] as JSX.Element[]);
+ }
+
+ componentDidMount() {
+ reaction(() => this.props.HistoBox.HistoOp.FilterString, () => this._selectedPrims.length = this.histoOp.FilterModels.length = 0);
+ }
+
+ private getSelectionToggle(binPrimitives: HistogramBinPrimitive[], allBrushIndex: number, filterModel: FilterModel) {
+ let rawAllBrushPrim = ArrayUtil.FirstOrDefault(binPrimitives, bp => bp.BrushIndex === allBrushIndex);
+ if (!rawAllBrushPrim) {
+ return emptyFunction;
+ }
+ let allBrushPrim = rawAllBrushPrim;
+ return () => runInAction(() => {
+ if (ArrayUtil.Contains(this.histoOp.FilterModels, filterModel)) {
+ this._selectedPrims.splice(this._selectedPrims.indexOf(allBrushPrim), 1);
+ this.histoOp.RemoveFilterModels([filterModel]);
+ }
+ else {
+ this._selectedPrims.push(allBrushPrim);
+ this.histoOp.AddFilterModels([filterModel]);
+ }
+ });
+ }
+
+ private renderGridLinesAndLabels(axis: number) {
+ trace();
+ if (!this.props.HistoBox.SizeConverter.Initialized) {
+ return (null);
+ }
+ let labels = this.props.HistoBox.VisualBinRanges[axis].GetLabels();
+ return <svg className="histogramboxprimitives-svgContainer">
+ {labels.reduce((prims, binLabel, i) => {
+ let r = this.props.HistoBox.SizeConverter.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
+ prims.push(this.drawLine(r.xFrom, r.yFrom, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0));
+ if (i === labels.length - 1) {
+ prims.push(this.drawLine(axis === 0 ? r.xTo : r.xFrom, axis === 0 ? r.yFrom : r.yTo, axis === 0 ? 0 : r.xTo - r.xFrom, axis === 0 ? r.yTo - r.yFrom : 0));
+ }
+ return prims;
+ }, [] as JSX.Element[])}
+ </svg>;
+ }
+
+ drawLine(xFrom: number, yFrom: number, width: number, height: number) {
+ if (height < 0) {
+ yFrom += height;
+ height = -height;
+ }
+ if (width < 0) {
+ xFrom += width;
+ width = -width;
+ }
+ let trans2Xpercent = `${(xFrom + width) / this.renderDimension * 100}%`;
+ let trans2Ypercent = `${(yFrom + height) / this.renderDimension * 100}%`;
+ let trans1Xpercent = `${xFrom / this.renderDimension * 100}%`;
+ let trans1Ypercent = `${yFrom / this.renderDimension * 100}%`;
+ return <line className="histogramboxprimitives-line" key={DashUtils.GenerateGuid()} x1={trans1Xpercent} x2={`${trans2Xpercent}`} y1={trans1Ypercent} y2={`${trans2Ypercent}`} />;
+ }
+ drawRect(r: PIXIRectangle, barAxis: number, color: number | undefined, classExt: string, tapHandler: () => void = emptyFunction) {
+ if (r.height < 0) {
+ r.y += r.height;
+ r.height = -r.height;
+ }
+ if (r.width < 0) {
+ r.x += r.width;
+ r.width = -r.width;
+ }
+ let transXpercent = `${r.x / this.renderDimension * 100}%`;
+ let transYpercent = `${r.y / this.renderDimension * 100}%`;
+ let widthXpercent = `${r.width / this.renderDimension * 100}%`;
+ let heightYpercent = `${r.height / this.renderDimension * 100}%`;
+ return (<rect className={`histogramboxprimitives-${classExt}`} key={DashUtils.GenerateGuid()} onPointerDown={(e: React.PointerEvent) => { if (e.button === 0) tapHandler(); }}
+ x={transXpercent} width={`${widthXpercent}`} y={transYpercent} height={`${heightYpercent}`} fill={color ? `${LABColor.RGBtoHexString(color)}` : "transparent"} />);
+ }
+ render() {
+ trace();
+ return <div className="histogramboxprimitives-container">
+ {this.xaxislines}
+ {this.yaxislines}
+ <svg className="histogramboxprimitives-svgContainer">
+ {this.barPrimitives}
+ {this.selectedPrimitives}
+ </svg>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
new file mode 100644
index 000000000..304d33771
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.scss
@@ -0,0 +1,13 @@
+
+ .histogramLabelPrimitives-gridlabel {
+ position:absolute;
+ transform-origin: left top;
+ font-size: 11;
+ color:white;
+ }
+ .histogramLabelPrimitives-placer {
+ position:absolute;
+ width:100%;
+ height:100%;
+ pointer-events: none;
+ } \ No newline at end of file
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
new file mode 100644
index 000000000..62aebd3c6
--- /dev/null
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -0,0 +1,80 @@
+import React = require("react");
+import { action, computed, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Utils as DashUtils } from '../../../Utils';
+import { NominalVisualBinRange } from "../model/binRanges/NominalVisualBinRange";
+import "../utils/Extensions";
+import { StyleConstants } from "../utils/StyleContants";
+import { HistogramBox } from "./HistogramBox";
+import "./HistogramLabelPrimitives.scss";
+import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
+
+@observer
+export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
+ componentDidMount() {
+ reaction(() => [this.props.HistoBox.props.PanelWidth(), this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
+ (fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0], fields[1], this.props.HistoBox), { fireImmediately: true });
+ }
+
+ @action
+ static computeLabelAngle(panelWidth: number, leftOffset: number, histoBox: HistogramBox) {
+ const textWidth = 30;
+ if (panelWidth > 0 && histoBox.VisualBinRanges.length && histoBox.VisualBinRanges[0] instanceof NominalVisualBinRange) {
+ let space = (panelWidth - leftOffset * 2) / histoBox.VisualBinRanges[0].GetBins().length;
+ histoBox.SizeConverter.SetLabelAngle(Math.min(Math.PI / 2, Math.max(Math.PI / 6, textWidth / space * Math.PI / 2)));
+ } else if (histoBox.SizeConverter.LabelAngle) {
+ histoBox.SizeConverter.SetLabelAngle(0);
+ }
+ }
+ @computed get xaxislines() { return this.renderGridLinesAndLabels(0); }
+ @computed get yaxislines() { return this.renderGridLinesAndLabels(1); }
+
+ private renderGridLinesAndLabels(axis: number) {
+ let sc = this.props.HistoBox.SizeConverter;
+ let vb = this.props.HistoBox.VisualBinRanges;
+ if (!vb.length || !sc.Initialized) {
+ return (null);
+ }
+ let dim = (axis === 0 ? this.props.HistoBox.props.PanelWidth() : this.props.HistoBox.props.PanelHeight()) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
+ (12 + 5) : // (<number>FontStyles.AxisLabel.fontSize + 5)));
+ sc.MaxLabelSizes[axis].coords[axis] + 5);
+
+ let labels = vb[axis].GetLabels();
+ return labels.reduce((prims, binLabel, i) => {
+ let r = sc.DataToScreenRange(binLabel.minValue!, binLabel.maxValue!, axis);
+ if (i % Math.ceil(labels.length / dim) === 0 && binLabel.label) {
+ const label = binLabel.label.Truncate(StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS, "...");
+ const textHeight = 14; const textWidth = 30;
+ let xStart = (axis === 0 ? r.xFrom + (r.xTo - r.xFrom) / 2.0 : r.xFrom - 10 - textWidth);
+ let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom);
+
+ if (axis === 0 && vb[axis] instanceof NominalVisualBinRange) {
+ let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.props.PanelWidth();
+ xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2;
+ }
+
+ let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`;
+ let yPercent = axis === 0 ? `${this.props.HistoBox.props.PanelHeight() - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
+
+ prims.push(
+ <div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
+ <div className="histogramLabelPrimitives-gridlabel" style={{ transform: `rotate(${axis === 0 ? sc.LabelAngle : 0}rad)` }}>
+ {label}
+ </div>
+ </div>
+ );
+ }
+ return prims;
+ }, [] as JSX.Element[]);
+ }
+
+ render() {
+ let xaxislines = this.xaxislines;
+ let yaxislines = this.yaxislines;
+ return <div className="histogramLabelPrimitives-container">
+ {xaxislines}
+ {yaxislines}
+ </div>;
+ }
+
+} \ No newline at end of file
diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts
new file mode 100644
index 000000000..c541cce6a
--- /dev/null
+++ b/src/client/northstar/manager/Gateway.ts
@@ -0,0 +1,299 @@
+import { Catalog, OperationReference, Result, CompileResults } from "../model/idea/idea";
+import { computed, observable, action } from "mobx";
+
+export class Gateway {
+
+ private static _instance: Gateway;
+
+ private constructor() {
+ }
+
+ public static get Instance() {
+ return this._instance || (this._instance = new this());
+ }
+
+ public async GetCatalog(): Promise<Catalog> {
+ try {
+ const json = await this.MakeGetRequest("catalog");
+ const cat = Catalog.fromJS(json);
+ return cat;
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
+ public async PostSchema(csvdata: string, schemaname: string): Promise<string> {
+ try {
+ const json = await this.MakePostJsonRequest("postSchema", { csv: csvdata, schema: schemaname });
+ // const cat = Catalog.fromJS(json);
+ // return cat;
+ return json;
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
+ public async GetSchema(pathname: string, schemaname: string): Promise<Catalog> {
+ try {
+ const json = await this.MakeGetRequest("schema", undefined, { path: pathname, schema: schemaname });
+ const cat = Catalog.fromJS(json);
+ return cat;
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
+ public async ClearCatalog(): Promise<void> {
+ try {
+ await this.MakePostJsonRequest("Datamart/ClearAllAugmentations", {});
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
+ public async TerminateServer(): Promise<void> {
+ try {
+ const url = Gateway.ConstructUrl("terminateServer");
+ const response = await fetch(url,
+ {
+ redirect: "follow",
+ method: "POST",
+ credentials: "include"
+ });
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
+ public async Compile(data: any): Promise<CompileResults | undefined> {
+ const json = await this.MakePostJsonRequest("compile", data);
+ if (json !== null) {
+ const cr = CompileResults.fromJS(json);
+ return cr;
+ }
+ }
+
+ public async SubmitResult(data: any): Promise<void> {
+ try {
+ console.log(data);
+ const url = Gateway.ConstructUrl("submitProblem");
+ const response = await fetch(url,
+ {
+ redirect: "follow",
+ method: "POST",
+ credentials: "include",
+ body: JSON.stringify(data)
+ });
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
+ public async SpecifyProblem(data: any): Promise<void> {
+ try {
+ console.log(data);
+ const url = Gateway.ConstructUrl("specifyProblem");
+ const response = await fetch(url,
+ {
+ redirect: "follow",
+ method: "POST",
+ credentials: "include",
+ body: JSON.stringify(data)
+ });
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
+ public async ExportToScript(solutionId: string): Promise<string> {
+ try {
+ const url = Gateway.ConstructUrl("exportsolution/script/" + solutionId);
+ const response = await fetch(url,
+ {
+ redirect: "follow",
+ method: "GET",
+ credentials: "include"
+ });
+ return await response.text();
+ }
+ catch (error) {
+ throw new Error("can not reach northstar's backend");
+ }
+ }
+
+
+ public async StartOperation(data: any): Promise<OperationReference | undefined> {
+ const json = await this.MakePostJsonRequest("operation", data);
+ if (json !== null) {
+ const or = OperationReference.fromJS(json);
+ return or;
+ }
+ }
+
+ public async GetResult(data: any): Promise<Result | undefined> {
+ const json = await this.MakePostJsonRequest("result", data);
+ if (json !== null) {
+ const res = Result.fromJS(json);
+ return res;
+ }
+ }
+
+ public async PauseOperation(data: any): Promise<void> {
+ const url = Gateway.ConstructUrl("pause");
+ await fetch(url,
+ {
+ redirect: "follow",
+ method: "POST",
+ credentials: "include",
+ body: JSON.stringify(data)
+ });
+ }
+
+ public async MakeGetRequest(endpoint: string, signal?: AbortSignal, params?: any): Promise<any> {
+ let url = !params ? Gateway.ConstructUrl(endpoint) :
+ (() => {
+ let newUrl = new URL(Gateway.ConstructUrl(endpoint));
+ Object.getOwnPropertyNames(params).map(prop =>
+ newUrl.searchParams.append(prop, params[prop]));
+ return Gateway.ConstructUrl(endpoint) + newUrl.search;
+ })();
+
+ const response = await fetch(url,
+ {
+ redirect: "follow",
+ method: "GET",
+ credentials: "include",
+ signal
+ });
+ const json = await response.json();
+ return json;
+ }
+
+ public async MakePostJsonRequest(endpoint: string, data: any, signal?: AbortSignal): Promise<any> {
+ const url = Gateway.ConstructUrl(endpoint);
+ const response = await fetch(url,
+ {
+ redirect: "follow",
+ method: "POST",
+ credentials: "include",
+ body: JSON.stringify(data),
+ signal
+ });
+ const json = await response.json();
+ return json;
+ }
+
+
+ public static ConstructUrl(appendix: string): string {
+ let base = NorthstarSettings.Instance.ServerUrl;
+ if (base.slice(-1) === "/") {
+ base = base.slice(0, -1);
+ }
+ let url = base + "/" + NorthstarSettings.Instance.ServerApiPath + "/" + appendix;
+ return url;
+ }
+}
+
+declare var ENV: any;
+
+export class NorthstarSettings {
+ private _environment: any;
+
+ @observable
+ public ServerUrl: string = document.URL;
+
+ @observable
+ public ServerApiPath?: string;
+
+ @observable
+ public SampleSize?: number;
+
+ @observable
+ public XBins?: number;
+
+ @observable
+ public YBins?: number;
+
+ @observable
+ public SplashTimeInMS?: number;
+
+ @observable
+ public ShowFpsCounter?: boolean;
+
+ @observable
+ public IsMenuFixed?: boolean;
+
+ @observable
+ public ShowShutdownButton?: boolean;
+
+ @observable
+ public IsDarpa?: boolean;
+
+ @observable
+ public IsIGT?: boolean;
+
+ @observable
+ public DegreeOfParallelism?: number;
+
+ @observable
+ public ShowWarnings?: boolean;
+
+ @computed
+ public get IsDev(): boolean {
+ return ENV.IsDev;
+ }
+
+ @computed
+ public get TestDataFolderPath(): string {
+ return this.Origin + "testdata/";
+ }
+
+ @computed
+ public get Origin(): string {
+ return window.location.origin + "/";
+ }
+
+ private static _instance: NorthstarSettings;
+
+ @action
+ public UpdateEnvironment(environment: any): void {
+ /*let serverParam = new URL(document.URL).searchParams.get("serverUrl");
+ if (serverParam) {
+ if (serverParam === "debug") {
+ this.ServerUrl = `http://${window.location.hostname}:1234`;
+ }
+ else {
+ this.ServerUrl = serverParam;
+ }
+ }
+ else {
+ this.ServerUrl = environment["SERVER_URL"] ? environment["SERVER_URL"] : document.URL;
+ }*/
+ this.ServerUrl = environment.SERVER_URL ? environment.SERVER_URL : document.URL;
+ this.ServerApiPath = environment.SERVER_API_PATH;
+ this.SampleSize = environment.SAMPLE_SIZE;
+ this.XBins = environment.X_BINS;
+ this.YBins = environment.Y_BINS;
+ this.SplashTimeInMS = environment.SPLASH_TIME_IN_MS;
+ this.ShowFpsCounter = environment.SHOW_FPS_COUNTER;
+ this.ShowShutdownButton = environment.SHOW_SHUTDOWN_BUTTON;
+ this.IsMenuFixed = environment.IS_MENU_FIXED;
+ this.IsDarpa = environment.IS_DARPA;
+ this.IsIGT = environment.IS_IGT;
+ this.DegreeOfParallelism = environment.DEGREE_OF_PARALLISM;
+ }
+
+ public static get Instance(): NorthstarSettings {
+ if (!this._instance) {
+ this._instance = new NorthstarSettings();
+ }
+ return this._instance;
+ }
+}
diff --git a/src/client/northstar/model/ModelExtensions.ts b/src/client/northstar/model/ModelExtensions.ts
new file mode 100644
index 000000000..29f80d2d1
--- /dev/null
+++ b/src/client/northstar/model/ModelExtensions.ts
@@ -0,0 +1,48 @@
+import { AttributeParameters, Brush, MarginAggregateParameters, SingleDimensionAggregateParameters, Solution } from '../model/idea/idea';
+import { Utils } from '../utils/Utils';
+
+import { FilterModel } from '../core/filter/FilterModel';
+
+(SingleDimensionAggregateParameters as any).prototype.Equals = function (other: Object) {
+ if (!Utils.EqualityHelper(this, other)) return false;
+ if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!,
+ (other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
+ if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters)) return false;
+ return true;
+};
+
+{
+ (AttributeParameters as any).prototype.Equals = function (other: AttributeParameters) {
+ return (this).constructor.name === (<any>other).constructor.name &&
+ this.rawName === other.rawName;
+ };
+}
+
+{
+ (Solution as any).prototype.Equals = function (other: Object) {
+ if (!Utils.EqualityHelper(this, other)) return false;
+ if ((this as Solution).solutionId !== (other as Solution).solutionId) return false;
+ return true;
+ };
+}
+
+{
+ (MarginAggregateParameters as any).prototype.Equals = function (other: Object) {
+ if (!Utils.EqualityHelper(this, other)) return false;
+ if (!Utils.EqualityHelper((this as SingleDimensionAggregateParameters).attributeParameters!,
+ (other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
+ if (!((this as SingleDimensionAggregateParameters).attributeParameters! as any).Equals((other as SingleDimensionAggregateParameters).attributeParameters!)) return false;
+
+ if ((this as MarginAggregateParameters).aggregateFunction !== (other as MarginAggregateParameters).aggregateFunction) return false;
+ return true;
+ };
+}
+
+{
+ (Brush as any).prototype.Equals = function (other: Object) {
+ if (!Utils.EqualityHelper(this, other)) return false;
+ if ((this as Brush).brushEnum !== (other as Brush).brushEnum) return false;
+ if ((this as Brush).brushIndex !== (other as Brush).brushIndex) return false;
+ return true;
+ };
+} \ No newline at end of file
diff --git a/src/client/northstar/model/ModelHelpers.ts b/src/client/northstar/model/ModelHelpers.ts
new file mode 100644
index 000000000..88e6e72b8
--- /dev/null
+++ b/src/client/northstar/model/ModelHelpers.ts
@@ -0,0 +1,220 @@
+
+import { action } from "mobx";
+import { AggregateFunction, AggregateKey, AggregateParameters, AttributeColumnParameters, AttributeParameters, AverageAggregateParameters, Bin, BinningParameters, Brush, BrushEnum, CountAggregateParameters, DataType, EquiWidthBinningParameters, HistogramResult, MarginAggregateParameters, SingleBinBinningParameters, SingleDimensionAggregateParameters, SumAggregateParameters, AggregateBinRange, NominalBinRange, AlphabeticBinRange, Predicate, Schema, Attribute, AttributeGroup, Exception, AttributeBackendParameters, AttributeCodeParameters } from '../model/idea/idea';
+import { ValueComparison } from "../core/filter/ValueComparision";
+import { ArrayUtil } from "../utils/ArrayUtil";
+import { AttributeModel, ColumnAttributeModel, BackendAttributeModel, CodeAttributeModel } from "../core/attribute/AttributeModel";
+import { FilterModel } from "../core/filter/FilterModel";
+import { AlphabeticVisualBinRange } from "./binRanges/AlphabeticVisualBinRange";
+import { NominalVisualBinRange } from "./binRanges/NominalVisualBinRange";
+import { VisualBinRangeHelper } from "./binRanges/VisualBinRangeHelper";
+import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+
+export class ModelHelpers {
+
+ public static CreateAggregateKey(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel, histogramResult: HistogramResult,
+ brushIndex: number, aggParameters?: SingleDimensionAggregateParameters): AggregateKey {
+ {
+ if (aggParameters === undefined) {
+ aggParameters = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, atm);
+ }
+ else {
+ aggParameters.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+ }
+ return new AggregateKey(
+ {
+ aggregateParameterIndex: ModelHelpers.GetAggregateParametersIndex(histogramResult, aggParameters),
+ brushIndex: brushIndex
+ });
+ }
+ }
+
+ public static GetAggregateParametersIndex(histogramResult: HistogramResult, aggParameters?: AggregateParameters): number {
+ return Array.from(histogramResult.aggregateParameters!).findIndex((value, i, set) => {
+ if (set[i] instanceof CountAggregateParameters && value instanceof CountAggregateParameters) return true;
+ if (set[i] instanceof MarginAggregateParameters && value instanceof MarginAggregateParameters) return true;
+ if (set[i] instanceof SumAggregateParameters && value instanceof SumAggregateParameters) return true;
+ return false;
+ });
+ }
+
+ public static GetAggregateParameter(distinctAttributeParameters: AttributeParameters | undefined, atm: AttributeTransformationModel): AggregateParameters | undefined {
+ var aggParam: AggregateParameters | undefined;
+ if (atm.AggregateFunction === AggregateFunction.Avg) {
+ var avg = new AverageAggregateParameters();
+ avg.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+ avg.distinctAttributeParameters = distinctAttributeParameters;
+ aggParam = avg;
+ }
+ else if (atm.AggregateFunction === AggregateFunction.Count) {
+ var cnt = new CountAggregateParameters();
+ cnt.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+ cnt.distinctAttributeParameters = distinctAttributeParameters;
+ aggParam = cnt;
+ }
+ else if (atm.AggregateFunction === AggregateFunction.Sum) {
+ var sum = new SumAggregateParameters();
+ sum.attributeParameters = ModelHelpers.GetAttributeParameters(atm.AttributeModel);
+ sum.distinctAttributeParameters = distinctAttributeParameters;
+ aggParam = sum;
+ }
+ return aggParam;
+ }
+
+ public static GetAggregateParametersWithMargins(distinctAttributeParameters: AttributeParameters | undefined, atms: Array<AttributeTransformationModel>): Array<AggregateParameters> {
+ var aggregateParameters = new Array<AggregateParameters>();
+ atms.forEach(agg => {
+ var aggParams = ModelHelpers.GetAggregateParameter(distinctAttributeParameters, agg);
+ if (aggParams) {
+ aggregateParameters.push(aggParams);
+
+ var margin = new MarginAggregateParameters();
+ margin.aggregateFunction = agg.AggregateFunction;
+ margin.attributeParameters = ModelHelpers.GetAttributeParameters(agg.AttributeModel);
+ margin.distinctAttributeParameters = distinctAttributeParameters;
+ aggregateParameters.push(margin);
+ }
+ });
+
+ return aggregateParameters;
+ }
+
+ public static GetBinningParameters(attr: AttributeTransformationModel, nrOfBins: number, minvalue?: number, maxvalue?: number): BinningParameters {
+ if (attr.AggregateFunction === AggregateFunction.None) {
+ return new EquiWidthBinningParameters(
+ {
+ attributeParameters: ModelHelpers.GetAttributeParameters(attr.AttributeModel),
+ requestedNrOfBins: nrOfBins,
+ minValue: minvalue,
+ maxValue: maxvalue
+ });
+ }
+ else {
+ return new SingleBinBinningParameters(
+ {
+ attributeParameters: ModelHelpers.GetAttributeParameters(attr.AttributeModel)
+ });
+ }
+ }
+
+ public static GetAttributeParametersFromAttributeModel(am: AttributeModel): AttributeParameters {
+ if (am instanceof ColumnAttributeModel) {
+ return new AttributeColumnParameters(
+ {
+ rawName: am.CodeName,
+ visualizationHints: am.VisualizationHints
+ });
+ }
+ else if (am instanceof BackendAttributeModel) {
+ return new AttributeBackendParameters(
+ {
+ rawName: am.CodeName,
+ visualizationHints: am.VisualizationHints,
+ id: (am).Id
+ });
+ }
+ else if (am instanceof CodeAttributeModel) {
+ return new AttributeCodeParameters(
+ {
+ rawName: am.CodeName,
+ visualizationHints: am.VisualizationHints,
+ code: (am).Code
+ });
+ }
+ else {
+ throw new Exception();
+ }
+ }
+
+ public static GetAttributeParameters(am: AttributeModel): AttributeParameters {
+ return this.GetAttributeParametersFromAttributeModel(am);
+ }
+
+ public static OverlapBrushIndex(histogramResult: HistogramResult): number {
+ var brush = ArrayUtil.First(histogramResult.brushes!, (b: any) => b.brushEnum === BrushEnum.Overlap);
+ return ModelHelpers.GetBrushIndex(histogramResult, brush);
+ }
+
+ public static AllBrushIndex(histogramResult: HistogramResult): number {
+ var brush = ArrayUtil.First(histogramResult.brushes!, (b: any) => b.brushEnum === BrushEnum.All);
+ return ModelHelpers.GetBrushIndex(histogramResult, brush);
+ }
+
+ public static RestBrushIndex(histogramResult: HistogramResult): number {
+ var brush = ArrayUtil.First(histogramResult.brushes!, (b: Brush) => b.brushEnum === BrushEnum.Rest);
+ return ModelHelpers.GetBrushIndex(histogramResult, brush);
+ }
+
+ public static GetBrushIndex(histogramResult: HistogramResult, brush: Brush): number {
+ return ArrayUtil.IndexOfWithEqual(histogramResult.brushes!, brush);
+ }
+
+ public static GetAggregateResult(bin: Bin, aggregateKey: AggregateKey) {
+ if (aggregateKey.aggregateParameterIndex === -1 || aggregateKey.brushIndex === -1) {
+ return null;
+ }
+ return bin.aggregateResults![aggregateKey.aggregateParameterIndex! * bin.ySize! + aggregateKey.brushIndex!];
+ }
+
+ @action
+ public static PossibleAggegationFunctions(atm: AttributeTransformationModel): Array<AggregateFunction> {
+ var ret = new Array<AggregateFunction>();
+ ret.push(AggregateFunction.None);
+ ret.push(AggregateFunction.Count);
+ if (atm.AttributeModel.DataType === DataType.Float ||
+ atm.AttributeModel.DataType === DataType.Double ||
+ atm.AttributeModel.DataType === DataType.Int) {
+ ret.push(AggregateFunction.Avg);
+ ret.push(AggregateFunction.Sum);
+ }
+ return ret;
+ }
+
+ public static GetBinFilterModel(
+ bin: Bin, brushIndex: number, histogramResult: HistogramResult,
+ xAom: AttributeTransformationModel, yAom: AttributeTransformationModel): FilterModel {
+ var dimensions: Array<AttributeTransformationModel> = [xAom, yAom];
+ var filterModel = new FilterModel();
+
+ for (var i = 0; i < histogramResult.binRanges!.length; i++) {
+ if (!(histogramResult.binRanges![i] instanceof AggregateBinRange)) {
+ var binRange = VisualBinRangeHelper.GetNonAggregateVisualBinRange(histogramResult.binRanges![i]);
+ var dataFrom = binRange.GetValueFromIndex(bin.binIndex!.indices![i]);
+ var dataTo = binRange.AddStep(dataFrom);
+
+ if (binRange instanceof NominalVisualBinRange) {
+ var tt = binRange.GetLabel(dataFrom);
+ filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.EQUALS, tt));
+ }
+ else if (binRange instanceof AlphabeticVisualBinRange) {
+ filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.STARTS_WITH,
+ binRange.GetLabel(dataFrom)));
+ }
+ else {
+ filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.GREATER_THAN_EQUAL, dataFrom));
+ filterModel.ValueComparisons.push(new ValueComparison(dimensions[i].AttributeModel, Predicate.LESS_THAN, dataTo));
+ }
+ }
+ }
+
+ return filterModel;
+ }
+
+ public GetAllAttributes(schema: Schema) {
+ if (!schema || !schema.rootAttributeGroup) {
+ return [];
+ }
+ const recurs = (attrs: Attribute[], g: AttributeGroup) => {
+ if (g.attributes) {
+ attrs.push.apply(attrs, g.attributes);
+ if (g.attributeGroups) {
+ g.attributeGroups.forEach(ng => recurs(attrs, ng));
+ }
+ }
+ };
+ const allAttributes: Attribute[] = new Array<Attribute>();
+ recurs(allAttributes, schema.rootAttributeGroup);
+ return allAttributes;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
new file mode 100644
index 000000000..120b034f2
--- /dev/null
+++ b/src/client/northstar/model/binRanges/AlphabeticVisualBinRange.ts
@@ -0,0 +1,52 @@
+import { AlphabeticBinRange, BinLabel } from '../../model/idea/idea';
+import { VisualBinRange } from './VisualBinRange';
+
+export class AlphabeticVisualBinRange extends VisualBinRange {
+ public DataBinRange: AlphabeticBinRange;
+
+ constructor(dataBinRange: AlphabeticBinRange) {
+ super();
+ this.DataBinRange = dataBinRange;
+ }
+
+ public AddStep(value: number): number {
+ return value + 1;
+ }
+
+ public GetValueFromIndex(index: number): number {
+ return index;
+ }
+
+ public GetBins(): number[] {
+ var bins = new Array<number>();
+ var idx = 0;
+ for (var key in this.DataBinRange.labelsValue) {
+ if (this.DataBinRange.labelsValue.hasOwnProperty(key)) {
+ bins.push(idx);
+ idx++;
+ }
+ }
+ return bins;
+ }
+
+ public GetLabel(value: number): string {
+ return this.DataBinRange.prefix + this.DataBinRange.valuesLabel![value];
+ }
+
+ public GetLabels(): Array<BinLabel> {
+ var labels = new Array<BinLabel>();
+ var count = 0;
+ for (var key in this.DataBinRange.valuesLabel) {
+ if (this.DataBinRange.valuesLabel.hasOwnProperty(key)) {
+ var value = this.DataBinRange.valuesLabel[key];
+ labels.push(new BinLabel({
+ value: parseFloat(key),
+ minValue: count++,
+ maxValue: count,
+ label: this.DataBinRange.prefix + value
+ }));
+ }
+ }
+ return labels;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
new file mode 100644
index 000000000..776e643cd
--- /dev/null
+++ b/src/client/northstar/model/binRanges/DateTimeVisualBinRange.ts
@@ -0,0 +1,105 @@
+import { DateTimeBinRange, DateTimeStep, DateTimeStepGranularity } from '../idea/idea';
+import { VisualBinRange } from './VisualBinRange';
+
+export class DateTimeVisualBinRange extends VisualBinRange {
+ public DataBinRange: DateTimeBinRange;
+
+ constructor(dataBinRange: DateTimeBinRange) {
+ super();
+ this.DataBinRange = dataBinRange;
+ }
+
+ public AddStep(value: number): number {
+ return DateTimeVisualBinRange.AddToDateTimeTicks(value, this.DataBinRange.step!);
+ }
+
+ public GetValueFromIndex(index: number): number {
+ var v = this.DataBinRange.minValue!;
+ for (var i = 0; i < index; i++) {
+ v = this.AddStep(v);
+ }
+ return v;
+ }
+
+ public GetBins(): number[] {
+ var bins = new Array<number>();
+ for (var v: number = this.DataBinRange.minValue!;
+ v < this.DataBinRange.maxValue!;
+ v = DateTimeVisualBinRange.AddToDateTimeTicks(v, this.DataBinRange.step!)) {
+ bins.push(v);
+ }
+ return bins;
+ }
+
+ private pad(n: number, size: number) {
+ var sign = n < 0 ? '-' : '';
+ return sign + new Array(size).concat([Math.abs(n)]).join('0').slice(-size);
+ }
+
+
+ public GetLabel(value: number): string {
+ var dt = DateTimeVisualBinRange.TicksToDate(value);
+ if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Second ||
+ this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Minute) {
+ return ("" + this.pad(dt.getMinutes(), 2) + ":" + this.pad(dt.getSeconds(), 2));
+ //return dt.ToString("mm:ss");
+ }
+ else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Hour) {
+ return (this.pad(dt.getHours(), 2) + ":" + this.pad(dt.getMinutes(), 2));
+ //return dt.ToString("HH:mm");
+ }
+ else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Day) {
+ return ((dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear());
+ //return dt.ToString("MM/dd/yyyy");
+ }
+ else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Month) {
+ //return dt.ToString("MM/yyyy");
+ return ((dt.getMonth() + 1) + "/" + dt.getFullYear());
+ }
+ else if (this.DataBinRange.step!.dateTimeStepGranularity === DateTimeStepGranularity.Year) {
+ return "" + dt.getFullYear();
+ }
+ return "n/a";
+ }
+
+ public static TicksToDate(ticks: number): Date {
+ var dd = new Date((ticks - 621355968000000000) / 10000);
+ dd.setMinutes(dd.getMinutes() + dd.getTimezoneOffset());
+ return dd;
+ }
+
+
+ public static DateToTicks(date: Date): number {
+ var copiedDate = new Date(date.getTime());
+ copiedDate.setMinutes(copiedDate.getMinutes() - copiedDate.getTimezoneOffset());
+ var t = copiedDate.getTime() * 10000 + 621355968000000000;
+ /*var dd = new Date((ticks - 621355968000000000) / 10000);
+ dd.setMinutes(dd.getMinutes() + dd.getTimezoneOffset());
+ return dd;*/
+ return t;
+ }
+
+ public static AddToDateTimeTicks(ticks: number, dateTimeStep: DateTimeStep): number {
+ var copiedDate = DateTimeVisualBinRange.TicksToDate(ticks);
+ var returnDate: Date = new Date(Date.now());
+ if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Second) {
+ returnDate = new Date(copiedDate.setSeconds(copiedDate.getSeconds() + dateTimeStep.dateTimeStepValue!));
+ }
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Minute) {
+ returnDate = new Date(copiedDate.setMinutes(copiedDate.getMinutes() + dateTimeStep.dateTimeStepValue!));
+ }
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Hour) {
+ returnDate = new Date(copiedDate.setHours(copiedDate.getHours() + dateTimeStep.dateTimeStepValue!));
+ }
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Day) {
+ returnDate = new Date(copiedDate.setDate(copiedDate.getDate() + dateTimeStep.dateTimeStepValue!));
+ }
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Month) {
+ returnDate = new Date(copiedDate.setMonth(copiedDate.getMonth() + dateTimeStep.dateTimeStepValue!));
+ }
+ else if (dateTimeStep.dateTimeStepGranularity === DateTimeStepGranularity.Year) {
+ returnDate = new Date(copiedDate.setFullYear(copiedDate.getFullYear() + dateTimeStep.dateTimeStepValue!));
+ }
+ return DateTimeVisualBinRange.DateToTicks(returnDate);
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/NominalVisualBinRange.ts b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
new file mode 100644
index 000000000..42509d797
--- /dev/null
+++ b/src/client/northstar/model/binRanges/NominalVisualBinRange.ts
@@ -0,0 +1,52 @@
+import { NominalBinRange, BinLabel } from '../../model/idea/idea';
+import { VisualBinRange } from './VisualBinRange';
+
+export class NominalVisualBinRange extends VisualBinRange {
+ public DataBinRange: NominalBinRange;
+
+ constructor(dataBinRange: NominalBinRange) {
+ super();
+ this.DataBinRange = dataBinRange;
+ }
+
+ public AddStep(value: number): number {
+ return value + 1;
+ }
+
+ public GetValueFromIndex(index: number): number {
+ return index;
+ }
+
+ public GetBins(): number[] {
+ var bins = new Array<number>();
+ var idx = 0;
+ for (var key in this.DataBinRange.labelsValue) {
+ if (this.DataBinRange.labelsValue.hasOwnProperty(key)) {
+ bins.push(idx);
+ idx++;
+ }
+ }
+ return bins;
+ }
+
+ public GetLabel(value: number): string {
+ return this.DataBinRange.valuesLabel![value];
+ }
+
+ public GetLabels(): Array<BinLabel> {
+ var labels = new Array<BinLabel>();
+ var count = 0;
+ for (var key in this.DataBinRange.valuesLabel) {
+ if (this.DataBinRange.valuesLabel.hasOwnProperty(key)) {
+ var value = this.DataBinRange.valuesLabel[key];
+ labels.push(new BinLabel({
+ value: parseFloat(key),
+ minValue: count++,
+ maxValue: count,
+ label: value
+ }));
+ }
+ }
+ return labels;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
new file mode 100644
index 000000000..c579c8e5f
--- /dev/null
+++ b/src/client/northstar/model/binRanges/QuantitativeVisualBinRange.ts
@@ -0,0 +1,90 @@
+import { QuantitativeBinRange } from '../idea/idea';
+import { VisualBinRange } from './VisualBinRange';
+import { format } from "d3-format";
+
+export class QuantitativeVisualBinRange extends VisualBinRange {
+
+ public DataBinRange: QuantitativeBinRange;
+
+ constructor(dataBinRange: QuantitativeBinRange) {
+ super();
+ this.DataBinRange = dataBinRange;
+ }
+
+ public AddStep(value: number): number {
+ return value + this.DataBinRange.step!;
+ }
+
+ public GetValueFromIndex(index: number): number {
+ return this.DataBinRange.minValue! + (index * this.DataBinRange.step!);
+ }
+
+ public GetLabel(value: number): string {
+ return QuantitativeVisualBinRange.NumberFormatter(value);
+ }
+
+ public static NumberFormatter(val: number): string {
+ if (val === 0) {
+ return "0";
+ }
+ if (val < 1) {
+ /*if (val < Math.abs(0.001)) {
+ return val.toExponential(2);
+ }*/
+ return format(".3")(val);
+ }
+ return format("~s")(val);
+ }
+
+ public GetBins(): number[] {
+ let bins = new Array<number>();
+
+ for (let v: number = this.DataBinRange.minValue!; v < this.DataBinRange.maxValue!; v += this.DataBinRange.step!) {
+ bins.push(v);
+ }
+ return bins;
+ }
+
+ public static Initialize(dataMinValue: number, dataMaxValue: number, targetBinNumber: number, isIntegerRange: boolean): QuantitativeVisualBinRange {
+ let extent = QuantitativeVisualBinRange.getExtent(dataMinValue, dataMaxValue, targetBinNumber, isIntegerRange);
+ let dataBinRange = new QuantitativeBinRange();
+ dataBinRange.minValue = extent[0];
+ dataBinRange.maxValue = extent[1];
+ dataBinRange.step = extent[2];
+
+ return new QuantitativeVisualBinRange(dataBinRange);
+ }
+
+ private static getExtent(dataMin: number, dataMax: number, m: number, isIntegerRange: boolean): number[] {
+ if (dataMin === dataMax) {
+ // dataMin -= 0.1;
+ dataMax += 0.1;
+ }
+ let span = dataMax - dataMin;
+
+ let step = Math.pow(10, Math.floor(Math.log10(span / m)));
+ let err = m / span * step;
+
+ if (err <= .15) {
+ step *= 10;
+ }
+ else if (err <= .35) {
+ step *= 5;
+ }
+ else if (err <= .75) {
+ step *= 2;
+ }
+
+ if (isIntegerRange) {
+ step = Math.ceil(step);
+ }
+ let ret: number[] = new Array<number>(3);
+ let minDivStep = Math.floor(dataMin / step);
+ let maxDivStep = Math.floor(dataMax / step);
+ ret[0] = minDivStep * step; // Math.floor(Math.Round(dataMin, 8)/step)*step;
+ ret[1] = maxDivStep * step + step; // Math.floor(Math.Round(dataMax, 8)/step)*step + step;
+ ret[2] = step;
+
+ return ret;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/VisualBinRange.ts b/src/client/northstar/model/binRanges/VisualBinRange.ts
new file mode 100644
index 000000000..449a22e91
--- /dev/null
+++ b/src/client/northstar/model/binRanges/VisualBinRange.ts
@@ -0,0 +1,32 @@
+import { BinLabel } from '../../model/idea/idea';
+
+export abstract class VisualBinRange {
+
+ public abstract AddStep(value: number): number;
+
+ public abstract GetValueFromIndex(index: number): number;
+
+ public abstract GetBins(): Array<number>;
+
+ public GetLabel(value: number): string {
+ return value.toString();
+ }
+
+ public GetLabels(): Array<BinLabel> {
+ var labels = new Array<BinLabel>();
+ var bins = this.GetBins();
+ bins.forEach(b => {
+ labels.push(new BinLabel({
+ value: b,
+ minValue: b,
+ maxValue: this.AddStep(b),
+ label: this.GetLabel(b)
+ }));
+ });
+ return labels;
+ }
+}
+
+export enum ChartType {
+ HorizontalBar = 0, VerticalBar = 1, HeatMap = 2, SinglePoint = 3
+} \ No newline at end of file
diff --git a/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
new file mode 100644
index 000000000..a92412686
--- /dev/null
+++ b/src/client/northstar/model/binRanges/VisualBinRangeHelper.ts
@@ -0,0 +1,70 @@
+import { BinRange, NominalBinRange, QuantitativeBinRange, Exception, AlphabeticBinRange, DateTimeBinRange, AggregateBinRange, DoubleValueAggregateResult, HistogramResult, AttributeParameters } from "../idea/idea";
+import { VisualBinRange, ChartType } from "./VisualBinRange";
+import { NominalVisualBinRange } from "./NominalVisualBinRange";
+import { QuantitativeVisualBinRange } from "./QuantitativeVisualBinRange";
+import { AlphabeticVisualBinRange } from "./AlphabeticVisualBinRange";
+import { DateTimeVisualBinRange } from "./DateTimeVisualBinRange";
+import { NorthstarSettings } from "../../manager/Gateway";
+import { ModelHelpers } from "../ModelHelpers";
+import { AttributeTransformationModel } from "../../core/attribute/AttributeTransformationModel";
+
+export const SETTINGS_X_BINS = 15;
+export const SETTINGS_Y_BINS = 15;
+export const SETTINGS_SAMPLE_SIZE = 100000;
+
+export class VisualBinRangeHelper {
+
+ public static GetNonAggregateVisualBinRange(dataBinRange: BinRange): VisualBinRange {
+ if (dataBinRange instanceof NominalBinRange) {
+ return new NominalVisualBinRange(dataBinRange);
+ }
+ else if (dataBinRange instanceof QuantitativeBinRange) {
+ return new QuantitativeVisualBinRange(dataBinRange);
+ }
+ else if (dataBinRange instanceof AlphabeticBinRange) {
+ return new AlphabeticVisualBinRange(dataBinRange);
+ }
+ else if (dataBinRange instanceof DateTimeBinRange) {
+ return new DateTimeVisualBinRange(dataBinRange);
+ }
+ throw new Exception();
+ }
+
+ public static GetVisualBinRange(distinctAttributeParameters: AttributeParameters | undefined, dataBinRange: BinRange, histoResult: HistogramResult, attr: AttributeTransformationModel, chartType: ChartType): VisualBinRange {
+
+ if (!(dataBinRange instanceof AggregateBinRange)) {
+ return VisualBinRangeHelper.GetNonAggregateVisualBinRange(dataBinRange);
+ }
+ else {
+ var aggregateKey = ModelHelpers.CreateAggregateKey(distinctAttributeParameters, attr, histoResult, ModelHelpers.AllBrushIndex(histoResult));
+ var minValue = Number.MAX_VALUE;
+ var maxValue = Number.MIN_VALUE;
+ for (const brush of histoResult.brushes!) {
+ aggregateKey.brushIndex = brush.brushIndex;
+ for (var key in histoResult.bins) {
+ if (histoResult.bins.hasOwnProperty(key)) {
+ var bin = histoResult.bins[key];
+ var res = <DoubleValueAggregateResult>ModelHelpers.GetAggregateResult(bin, aggregateKey);
+ if (res && res.hasResult && res.result) {
+ minValue = Math.min(minValue, res.result);
+ maxValue = Math.max(maxValue, res.result);
+ }
+ }
+ }
+ }
+
+ let visualBinRange = QuantitativeVisualBinRange.Initialize(minValue, maxValue, 10, false);
+
+ if (chartType === ChartType.HorizontalBar || chartType === ChartType.VerticalBar) {
+ visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue),
+ Math.max(0, (visualBinRange).DataBinRange.maxValue!),
+ SETTINGS_X_BINS, false);
+ }
+ else if (chartType === ChartType.SinglePoint) {
+ visualBinRange = QuantitativeVisualBinRange.Initialize(Math.min(0, minValue), Math.max(0, maxValue),
+ SETTINGS_X_BINS, false);
+ }
+ return visualBinRange;
+ }
+ }
+}
diff --git a/src/client/northstar/model/idea/MetricTypeMapping.ts b/src/client/northstar/model/idea/MetricTypeMapping.ts
new file mode 100644
index 000000000..e9759cf16
--- /dev/null
+++ b/src/client/northstar/model/idea/MetricTypeMapping.ts
@@ -0,0 +1,30 @@
+import { MetricType } from "./Idea";
+import { Dictionary } from 'typescript-collections';
+
+
+export class MetricTypeMapping {
+
+ public static GetMetricInterpretation(metricType: MetricType): MetricInterpretation {
+ if (metricType === MetricType.Accuracy ||
+ metricType === MetricType.F1 ||
+ metricType === MetricType.F1Macro ||
+ metricType === MetricType.F1Micro ||
+ metricType === MetricType.JaccardSimilarityScore ||
+ metricType === MetricType.ObjectDetectionAveragePrecision ||
+ metricType === MetricType.Precision ||
+ metricType === MetricType.PrecisionAtTopK ||
+ metricType === MetricType.NormalizedMutualInformation ||
+ metricType === MetricType.Recall ||
+ metricType === MetricType.RocAucMacro ||
+ metricType === MetricType.RocAuc ||
+ metricType === MetricType.RocAucMicro ||
+ metricType === MetricType.RSquared) {
+ return MetricInterpretation.HigherIsBetter;
+ }
+ return MetricInterpretation.LowerIsBetter;
+ }
+}
+
+export enum MetricInterpretation {
+ HigherIsBetter, LowerIsBetter
+} \ No newline at end of file
diff --git a/src/client/northstar/model/idea/idea.ts b/src/client/northstar/model/idea/idea.ts
new file mode 100644
index 000000000..c73a822c7
--- /dev/null
+++ b/src/client/northstar/model/idea/idea.ts
@@ -0,0 +1,8557 @@
+/* tslint:disable */
+//----------------------
+// <auto-generated>
+// Generated using the NSwag toolchain v11.19.2.0 (NJsonSchema v9.10.73.0 (Newtonsoft.Json v9.0.0.0)) (http://NSwag.org)
+// </auto-generated>
+//----------------------
+// ReSharper disable InconsistentNaming
+
+
+
+export enum AggregateFunction {
+ None = "None",
+ Sum = "Sum",
+ SumE = "SumE",
+ Count = "Count",
+ Min = "Min",
+ Max = "Max",
+ Avg = "Avg",
+}
+
+export abstract class AggregateParameters implements IAggregateParameters {
+
+ protected _discriminator: string;
+
+ public Equals(other: Object): boolean {
+ return this == other;
+ }
+ constructor(data?: IAggregateParameters) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "AggregateParameters";
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): AggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "AverageAggregateParameters") {
+ let result = new AverageAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SingleDimensionAggregateParameters") {
+ throw new Error("The abstract class 'SingleDimensionAggregateParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "CountAggregateParameters") {
+ let result = new CountAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "KDEAggregateParameters") {
+ let result = new KDEAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "MarginAggregateParameters") {
+ let result = new MarginAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "MaxAggregateParameters") {
+ let result = new MaxAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "MinAggregateParameters") {
+ let result = new MinAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SumAggregateParameters") {
+ let result = new SumAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SumEstimationAggregateParameters") {
+ let result = new SumEstimationAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'AggregateParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ return data;
+ }
+}
+
+export interface IAggregateParameters {
+}
+
+export abstract class SingleDimensionAggregateParameters extends AggregateParameters implements ISingleDimensionAggregateParameters {
+ attributeParameters?: AttributeParameters | undefined;
+ distinctAttributeParameters?: AttributeParameters | undefined;
+
+ constructor(data?: ISingleDimensionAggregateParameters) {
+ super(data);
+ this._discriminator = "SingleDimensionAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.attributeParameters = data["AttributeParameters"] ? AttributeParameters.fromJS(data["AttributeParameters"]) : <any>undefined;
+ this.distinctAttributeParameters = data["DistinctAttributeParameters"] ? AttributeParameters.fromJS(data["DistinctAttributeParameters"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): SingleDimensionAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "AverageAggregateParameters") {
+ let result = new AverageAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CountAggregateParameters") {
+ let result = new CountAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "KDEAggregateParameters") {
+ let result = new KDEAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "MarginAggregateParameters") {
+ let result = new MarginAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "MaxAggregateParameters") {
+ let result = new MaxAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "MinAggregateParameters") {
+ let result = new MinAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SumAggregateParameters") {
+ let result = new SumAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SumEstimationAggregateParameters") {
+ let result = new SumEstimationAggregateParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'SingleDimensionAggregateParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["AttributeParameters"] = this.attributeParameters ? this.attributeParameters.toJSON() : <any>undefined;
+ data["DistinctAttributeParameters"] = this.distinctAttributeParameters ? this.distinctAttributeParameters.toJSON() : <any>undefined;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISingleDimensionAggregateParameters extends IAggregateParameters {
+ attributeParameters?: AttributeParameters | undefined;
+ distinctAttributeParameters?: AttributeParameters | undefined;
+}
+
+export class AverageAggregateParameters extends SingleDimensionAggregateParameters implements IAverageAggregateParameters {
+
+ constructor(data?: IAverageAggregateParameters) {
+ super(data);
+ this._discriminator = "AverageAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): AverageAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new AverageAggregateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAverageAggregateParameters extends ISingleDimensionAggregateParameters {
+}
+
+export abstract class AttributeParameters implements IAttributeParameters {
+ visualizationHints?: VisualizationHint[] | undefined;
+ rawName?: string | undefined;
+ public Equals(other: Object): boolean {
+ return this == other;
+ }
+
+ protected _discriminator: string;
+
+ constructor(data?: IAttributeParameters) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "AttributeParameters";
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["VisualizationHints"] && data["VisualizationHints"].constructor === Array) {
+ this.visualizationHints = [];
+ for (let item of data["VisualizationHints"])
+ this.visualizationHints.push(item);
+ }
+ this.rawName = data["RawName"];
+ }
+ }
+
+ static fromJS(data: any): AttributeParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "AttributeBackendParameters") {
+ let result = new AttributeBackendParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AttributeCaclculatedParameters") {
+ throw new Error("The abstract class 'AttributeCaclculatedParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "AttributeCodeParameters") {
+ let result = new AttributeCodeParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AttributeColumnParameters") {
+ let result = new AttributeColumnParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'AttributeParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ if (this.visualizationHints && this.visualizationHints.constructor === Array) {
+ data["VisualizationHints"] = [];
+ for (let item of this.visualizationHints)
+ data["VisualizationHints"].push(item);
+ }
+ data["RawName"] = this.rawName;
+ return data;
+ }
+}
+
+export interface IAttributeParameters {
+ visualizationHints?: VisualizationHint[] | undefined;
+ rawName?: string | undefined;
+}
+
+export enum VisualizationHint {
+ TreatAsEnumeration = "TreatAsEnumeration",
+ DefaultFlipAxis = "DefaultFlipAxis",
+ Image = "Image",
+}
+
+export abstract class AttributeCaclculatedParameters extends AttributeParameters implements IAttributeCaclculatedParameters {
+
+ constructor(data?: IAttributeCaclculatedParameters) {
+ super(data);
+ this._discriminator = "AttributeCaclculatedParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): AttributeCaclculatedParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "AttributeBackendParameters") {
+ let result = new AttributeBackendParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AttributeCodeParameters") {
+ let result = new AttributeCodeParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'AttributeCaclculatedParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAttributeCaclculatedParameters extends IAttributeParameters {
+}
+
+export class AttributeBackendParameters extends AttributeCaclculatedParameters implements IAttributeBackendParameters {
+ id?: string | undefined;
+
+ constructor(data?: IAttributeBackendParameters) {
+ super(data);
+ this._discriminator = "AttributeBackendParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.id = data["Id"];
+ }
+ }
+
+ static fromJS(data: any): AttributeBackendParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new AttributeBackendParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Id"] = this.id;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAttributeBackendParameters extends IAttributeCaclculatedParameters {
+ id?: string | undefined;
+}
+
+export class AttributeCodeParameters extends AttributeCaclculatedParameters implements IAttributeCodeParameters {
+ code?: string | undefined;
+
+ constructor(data?: IAttributeCodeParameters) {
+ super(data);
+ this._discriminator = "AttributeCodeParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.code = data["Code"];
+ }
+ }
+
+ static fromJS(data: any): AttributeCodeParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new AttributeCodeParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Code"] = this.code;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAttributeCodeParameters extends IAttributeCaclculatedParameters {
+ code?: string | undefined;
+}
+
+export class AttributeColumnParameters extends AttributeParameters implements IAttributeColumnParameters {
+
+ constructor(data?: IAttributeColumnParameters) {
+ super(data);
+ this._discriminator = "AttributeColumnParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): AttributeColumnParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new AttributeColumnParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAttributeColumnParameters extends IAttributeParameters {
+}
+
+export class CountAggregateParameters extends SingleDimensionAggregateParameters implements ICountAggregateParameters {
+
+ constructor(data?: ICountAggregateParameters) {
+ super(data);
+ this._discriminator = "CountAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): CountAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new CountAggregateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ICountAggregateParameters extends ISingleDimensionAggregateParameters {
+}
+
+export class KDEAggregateParameters extends SingleDimensionAggregateParameters implements IKDEAggregateParameters {
+ nrOfSamples?: number | undefined;
+
+ constructor(data?: IKDEAggregateParameters) {
+ super(data);
+ this._discriminator = "KDEAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.nrOfSamples = data["NrOfSamples"];
+ }
+ }
+
+ static fromJS(data: any): KDEAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new KDEAggregateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["NrOfSamples"] = this.nrOfSamples;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IKDEAggregateParameters extends ISingleDimensionAggregateParameters {
+ nrOfSamples?: number | undefined;
+}
+
+export class MarginAggregateParameters extends SingleDimensionAggregateParameters implements IMarginAggregateParameters {
+ aggregateFunction?: AggregateFunction | undefined;
+
+ constructor(data?: IMarginAggregateParameters) {
+ super(data);
+ this._discriminator = "MarginAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.aggregateFunction = data["AggregateFunction"];
+ }
+ }
+
+ static fromJS(data: any): MarginAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new MarginAggregateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["AggregateFunction"] = this.aggregateFunction;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IMarginAggregateParameters extends ISingleDimensionAggregateParameters {
+ aggregateFunction?: AggregateFunction | undefined;
+}
+
+export class MaxAggregateParameters extends SingleDimensionAggregateParameters implements IMaxAggregateParameters {
+
+ constructor(data?: IMaxAggregateParameters) {
+ super(data);
+ this._discriminator = "MaxAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): MaxAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new MaxAggregateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IMaxAggregateParameters extends ISingleDimensionAggregateParameters {
+}
+
+export class MinAggregateParameters extends SingleDimensionAggregateParameters implements IMinAggregateParameters {
+
+ constructor(data?: IMinAggregateParameters) {
+ super(data);
+ this._discriminator = "MinAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): MinAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new MinAggregateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IMinAggregateParameters extends ISingleDimensionAggregateParameters {
+}
+
+export class SumAggregateParameters extends SingleDimensionAggregateParameters implements ISumAggregateParameters {
+
+ constructor(data?: ISumAggregateParameters) {
+ super(data);
+ this._discriminator = "SumAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): SumAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new SumAggregateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISumAggregateParameters extends ISingleDimensionAggregateParameters {
+}
+
+export class SumEstimationAggregateParameters extends SingleDimensionAggregateParameters implements ISumEstimationAggregateParameters {
+
+ constructor(data?: ISumEstimationAggregateParameters) {
+ super(data);
+ this._discriminator = "SumEstimationAggregateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): SumEstimationAggregateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new SumEstimationAggregateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISumEstimationAggregateParameters extends ISingleDimensionAggregateParameters {
+}
+
+export enum OrderingFunction {
+ None = 0,
+ SortUp = 1,
+ SortDown = 2,
+}
+
+export abstract class BinningParameters implements IBinningParameters {
+ attributeParameters?: AttributeParameters | undefined;
+
+ protected _discriminator: string;
+
+ constructor(data?: IBinningParameters) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "BinningParameters";
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.attributeParameters = data["AttributeParameters"] ? AttributeParameters.fromJS(data["AttributeParameters"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): BinningParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "EquiWidthBinningParameters") {
+ let result = new EquiWidthBinningParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SingleBinBinningParameters") {
+ let result = new SingleBinBinningParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'BinningParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ data["AttributeParameters"] = this.attributeParameters ? this.attributeParameters.toJSON() : <any>undefined;
+ return data;
+ }
+}
+
+export interface IBinningParameters {
+ attributeParameters?: AttributeParameters | undefined;
+}
+
+export class EquiWidthBinningParameters extends BinningParameters implements IEquiWidthBinningParameters {
+ minValue?: number | undefined;
+ maxValue?: number | undefined;
+ requestedNrOfBins?: number | undefined;
+ referenceValue?: number | undefined;
+ step?: number | undefined;
+
+ constructor(data?: IEquiWidthBinningParameters) {
+ super(data);
+ this._discriminator = "EquiWidthBinningParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.minValue = data["MinValue"];
+ this.maxValue = data["MaxValue"];
+ this.requestedNrOfBins = data["RequestedNrOfBins"];
+ this.referenceValue = data["ReferenceValue"];
+ this.step = data["Step"];
+ }
+ }
+
+ static fromJS(data: any): EquiWidthBinningParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new EquiWidthBinningParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["MinValue"] = this.minValue;
+ data["MaxValue"] = this.maxValue;
+ data["RequestedNrOfBins"] = this.requestedNrOfBins;
+ data["ReferenceValue"] = this.referenceValue;
+ data["Step"] = this.step;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IEquiWidthBinningParameters extends IBinningParameters {
+ minValue?: number | undefined;
+ maxValue?: number | undefined;
+ requestedNrOfBins?: number | undefined;
+ referenceValue?: number | undefined;
+ step?: number | undefined;
+}
+
+export class SingleBinBinningParameters extends BinningParameters implements ISingleBinBinningParameters {
+
+ constructor(data?: ISingleBinBinningParameters) {
+ super(data);
+ this._discriminator = "SingleBinBinningParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): SingleBinBinningParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new SingleBinBinningParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISingleBinBinningParameters extends IBinningParameters {
+}
+
+export class Attribute implements IAttribute {
+ displayName?: string | undefined;
+ rawName?: string | undefined;
+ description?: string | undefined;
+ dataType?: DataType | undefined;
+ visualizationHints?: VisualizationHint[] | undefined;
+ isTarget?: boolean | undefined;
+
+ constructor(data?: IAttribute) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.displayName = data["DisplayName"];
+ this.rawName = data["RawName"];
+ this.description = data["Description"];
+ this.dataType = data["DataType"];
+ if (data["VisualizationHints"] && data["VisualizationHints"].constructor === Array) {
+ this.visualizationHints = [];
+ for (let item of data["VisualizationHints"])
+ this.visualizationHints.push(item);
+ }
+ this.isTarget = data["IsTarget"];
+ }
+ }
+
+ static fromJS(data: any): Attribute {
+ data = typeof data === 'object' ? data : {};
+ let result = new Attribute();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["DisplayName"] = this.displayName;
+ data["RawName"] = this.rawName;
+ data["Description"] = this.description;
+ data["DataType"] = this.dataType;
+ if (this.visualizationHints && this.visualizationHints.constructor === Array) {
+ data["VisualizationHints"] = [];
+ for (let item of this.visualizationHints)
+ data["VisualizationHints"].push(item);
+ }
+ data["IsTarget"] = this.isTarget;
+ return data;
+ }
+}
+
+export interface IAttribute {
+ displayName?: string | undefined;
+ rawName?: string | undefined;
+ description?: string | undefined;
+ dataType?: DataType | undefined;
+ visualizationHints?: VisualizationHint[] | undefined;
+ isTarget?: boolean | undefined;
+}
+
+export enum DataType {
+ Int = "Int",
+ String = "String",
+ Float = "Float",
+ Double = "Double",
+ DateTime = "DateTime",
+ Object = "Object",
+ Undefined = "Undefined",
+}
+
+export class AttributeGroup implements IAttributeGroup {
+ name?: string | undefined;
+ attributeGroups?: AttributeGroup[] | undefined;
+ attributes?: Attribute[] | undefined;
+
+ constructor(data?: IAttributeGroup) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.name = data["Name"];
+ if (data["AttributeGroups"] && data["AttributeGroups"].constructor === Array) {
+ this.attributeGroups = [];
+ for (let item of data["AttributeGroups"])
+ this.attributeGroups.push(AttributeGroup.fromJS(item));
+ }
+ if (data["Attributes"] && data["Attributes"].constructor === Array) {
+ this.attributes = [];
+ for (let item of data["Attributes"])
+ this.attributes.push(Attribute.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): AttributeGroup {
+ data = typeof data === 'object' ? data : {};
+ let result = new AttributeGroup();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Name"] = this.name;
+ if (this.attributeGroups && this.attributeGroups.constructor === Array) {
+ data["AttributeGroups"] = [];
+ for (let item of this.attributeGroups)
+ data["AttributeGroups"].push(item.toJSON());
+ }
+ if (this.attributes && this.attributes.constructor === Array) {
+ data["Attributes"] = [];
+ for (let item of this.attributes)
+ data["Attributes"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface IAttributeGroup {
+ name?: string | undefined;
+ attributeGroups?: AttributeGroup[] | undefined;
+ attributes?: Attribute[] | undefined;
+}
+
+export class Catalog implements ICatalog {
+ supportedOperations?: string[] | undefined;
+ schemas?: Schema[] | undefined;
+
+ constructor(data?: ICatalog) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["SupportedOperations"] && data["SupportedOperations"].constructor === Array) {
+ this.supportedOperations = [];
+ for (let item of data["SupportedOperations"])
+ this.supportedOperations.push(item);
+ }
+ if (data["Schemas"] && data["Schemas"].constructor === Array) {
+ this.schemas = [];
+ for (let item of data["Schemas"])
+ this.schemas.push(Schema.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): Catalog {
+ data = typeof data === 'object' ? data : {};
+ let result = new Catalog();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.supportedOperations && this.supportedOperations.constructor === Array) {
+ data["SupportedOperations"] = [];
+ for (let item of this.supportedOperations)
+ data["SupportedOperations"].push(item);
+ }
+ if (this.schemas && this.schemas.constructor === Array) {
+ data["Schemas"] = [];
+ for (let item of this.schemas)
+ data["Schemas"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface ICatalog {
+ supportedOperations?: string[] | undefined;
+ schemas?: Schema[] | undefined;
+}
+
+export class Schema implements ISchema {
+ rootAttributeGroup?: AttributeGroup | undefined;
+ displayName?: string | undefined;
+ augmentedFrom?: string | undefined;
+ rawName?: string | undefined;
+ problemDescription?: string | undefined;
+ darpaProblemDoc?: DarpaProblemDoc | undefined;
+ distinctAttributeParameters?: AttributeParameters | undefined;
+ darpaDatasetDoc?: DarpaDatasetDoc | undefined;
+ darpaDatasetLocation?: string | undefined;
+ isMultiResourceData?: boolean | undefined;
+ problemFinderRows?: ProblemFinderRows[] | undefined;
+ correlationRows?: ProblemFinderRows[] | undefined;
+
+ constructor(data?: ISchema) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.rootAttributeGroup = data["RootAttributeGroup"] ? AttributeGroup.fromJS(data["RootAttributeGroup"]) : <any>undefined;
+ this.displayName = data["DisplayName"];
+ this.augmentedFrom = data["AugmentedFrom"];
+ this.rawName = data["RawName"];
+ this.problemDescription = data["ProblemDescription"];
+ this.darpaProblemDoc = data["DarpaProblemDoc"] ? DarpaProblemDoc.fromJS(data["DarpaProblemDoc"]) : <any>undefined;
+ this.distinctAttributeParameters = data["DistinctAttributeParameters"] ? AttributeParameters.fromJS(data["DistinctAttributeParameters"]) : <any>undefined;
+ this.darpaDatasetDoc = data["DarpaDatasetDoc"] ? DarpaDatasetDoc.fromJS(data["DarpaDatasetDoc"]) : <any>undefined;
+ this.darpaDatasetLocation = data["DarpaDatasetLocation"];
+ this.isMultiResourceData = data["IsMultiResourceData"];
+ if (data["ProblemFinderRows"] && data["ProblemFinderRows"].constructor === Array) {
+ this.problemFinderRows = [];
+ for (let item of data["ProblemFinderRows"])
+ this.problemFinderRows.push(ProblemFinderRows.fromJS(item));
+ }
+ if (data["CorrelationRows"] && data["CorrelationRows"].constructor === Array) {
+ this.correlationRows = [];
+ for (let item of data["CorrelationRows"])
+ this.correlationRows.push(ProblemFinderRows.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): Schema {
+ data = typeof data === 'object' ? data : {};
+ let result = new Schema();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["RootAttributeGroup"] = this.rootAttributeGroup ? this.rootAttributeGroup.toJSON() : <any>undefined;
+ data["DisplayName"] = this.displayName;
+ data["AugmentedFrom"] = this.augmentedFrom;
+ data["RawName"] = this.rawName;
+ data["ProblemDescription"] = this.problemDescription;
+ data["DarpaProblemDoc"] = this.darpaProblemDoc ? this.darpaProblemDoc.toJSON() : <any>undefined;
+ data["DistinctAttributeParameters"] = this.distinctAttributeParameters ? this.distinctAttributeParameters.toJSON() : <any>undefined;
+ data["DarpaDatasetDoc"] = this.darpaDatasetDoc ? this.darpaDatasetDoc.toJSON() : <any>undefined;
+ data["DarpaDatasetLocation"] = this.darpaDatasetLocation;
+ data["IsMultiResourceData"] = this.isMultiResourceData;
+ if (this.problemFinderRows && this.problemFinderRows.constructor === Array) {
+ data["ProblemFinderRows"] = [];
+ for (let item of this.problemFinderRows)
+ data["ProblemFinderRows"].push(item.toJSON());
+ }
+ if (this.correlationRows && this.correlationRows.constructor === Array) {
+ data["CorrelationRows"] = [];
+ for (let item of this.correlationRows)
+ data["CorrelationRows"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface ISchema {
+ rootAttributeGroup?: AttributeGroup | undefined;
+ displayName?: string | undefined;
+ augmentedFrom?: string | undefined;
+ rawName?: string | undefined;
+ problemDescription?: string | undefined;
+ darpaProblemDoc?: DarpaProblemDoc | undefined;
+ distinctAttributeParameters?: AttributeParameters | undefined;
+ darpaDatasetDoc?: DarpaDatasetDoc | undefined;
+ darpaDatasetLocation?: string | undefined;
+ isMultiResourceData?: boolean | undefined;
+ problemFinderRows?: ProblemFinderRows[] | undefined;
+ correlationRows?: ProblemFinderRows[] | undefined;
+}
+
+export class DarpaProblemDoc implements IDarpaProblemDoc {
+ about?: ProblemAbout | undefined;
+ inputs?: ProblemInputs | undefined;
+
+ constructor(data?: IDarpaProblemDoc) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.about = data["about"] ? ProblemAbout.fromJS(data["about"]) : <any>undefined;
+ this.inputs = data["inputs"] ? ProblemInputs.fromJS(data["inputs"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): DarpaProblemDoc {
+ data = typeof data === 'object' ? data : {};
+ let result = new DarpaProblemDoc();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["about"] = this.about ? this.about.toJSON() : <any>undefined;
+ data["inputs"] = this.inputs ? this.inputs.toJSON() : <any>undefined;
+ return data;
+ }
+}
+
+export interface IDarpaProblemDoc {
+ about?: ProblemAbout | undefined;
+ inputs?: ProblemInputs | undefined;
+}
+
+export class ProblemAbout implements IProblemAbout {
+ problemID?: string | undefined;
+ problemName?: string | undefined;
+ problemDescription?: string | undefined;
+ taskType?: string | undefined;
+ taskSubType?: string | undefined;
+ problemSchemaVersion?: string | undefined;
+ problemVersion?: string | undefined;
+
+ constructor(data?: IProblemAbout) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.problemID = data["problemID"];
+ this.problemName = data["problemName"];
+ this.problemDescription = data["problemDescription"];
+ this.taskType = data["taskType"];
+ this.taskSubType = data["taskSubType"];
+ this.problemSchemaVersion = data["problemSchemaVersion"];
+ this.problemVersion = data["problemVersion"];
+ }
+ }
+
+ static fromJS(data: any): ProblemAbout {
+ data = typeof data === 'object' ? data : {};
+ let result = new ProblemAbout();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["problemID"] = this.problemID;
+ data["problemName"] = this.problemName;
+ data["problemDescription"] = this.problemDescription;
+ data["taskType"] = this.taskType;
+ data["taskSubType"] = this.taskSubType;
+ data["problemSchemaVersion"] = this.problemSchemaVersion;
+ data["problemVersion"] = this.problemVersion;
+ return data;
+ }
+}
+
+export interface IProblemAbout {
+ problemID?: string | undefined;
+ problemName?: string | undefined;
+ problemDescription?: string | undefined;
+ taskType?: string | undefined;
+ taskSubType?: string | undefined;
+ problemSchemaVersion?: string | undefined;
+ problemVersion?: string | undefined;
+}
+
+export class ProblemInputs implements IProblemInputs {
+ data?: ProblemData[] | undefined;
+ performanceMetrics?: ProblemPerformanceMetric[] | undefined;
+
+ constructor(data?: IProblemInputs) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["data"] && data["data"].constructor === Array) {
+ this.data = [];
+ for (let item of data["data"])
+ this.data.push(ProblemData.fromJS(item));
+ }
+ if (data["performanceMetrics"] && data["performanceMetrics"].constructor === Array) {
+ this.performanceMetrics = [];
+ for (let item of data["performanceMetrics"])
+ this.performanceMetrics.push(ProblemPerformanceMetric.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): ProblemInputs {
+ data = typeof data === 'object' ? data : {};
+ let result = new ProblemInputs();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.data && this.data.constructor === Array) {
+ data["data"] = [];
+ for (let item of this.data)
+ data["data"].push(item.toJSON());
+ }
+ if (this.performanceMetrics && this.performanceMetrics.constructor === Array) {
+ data["performanceMetrics"] = [];
+ for (let item of this.performanceMetrics)
+ data["performanceMetrics"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface IProblemInputs {
+ data?: ProblemData[] | undefined;
+ performanceMetrics?: ProblemPerformanceMetric[] | undefined;
+}
+
+export class ProblemData implements IProblemData {
+ datasetID?: string | undefined;
+ targets?: ProblemTarget[] | undefined;
+
+ constructor(data?: IProblemData) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.datasetID = data["datasetID"];
+ if (data["targets"] && data["targets"].constructor === Array) {
+ this.targets = [];
+ for (let item of data["targets"])
+ this.targets.push(ProblemTarget.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): ProblemData {
+ data = typeof data === 'object' ? data : {};
+ let result = new ProblemData();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["datasetID"] = this.datasetID;
+ if (this.targets && this.targets.constructor === Array) {
+ data["targets"] = [];
+ for (let item of this.targets)
+ data["targets"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface IProblemData {
+ datasetID?: string | undefined;
+ targets?: ProblemTarget[] | undefined;
+}
+
+export class ProblemTarget implements IProblemTarget {
+ targetIndex?: number | undefined;
+ resID?: string | undefined;
+ colIndex?: number | undefined;
+ colName?: string | undefined;
+
+ constructor(data?: IProblemTarget) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.targetIndex = data["targetIndex"];
+ this.resID = data["resID"];
+ this.colIndex = data["colIndex"];
+ this.colName = data["colName"];
+ }
+ }
+
+ static fromJS(data: any): ProblemTarget {
+ data = typeof data === 'object' ? data : {};
+ let result = new ProblemTarget();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["targetIndex"] = this.targetIndex;
+ data["resID"] = this.resID;
+ data["colIndex"] = this.colIndex;
+ data["colName"] = this.colName;
+ return data;
+ }
+}
+
+export interface IProblemTarget {
+ targetIndex?: number | undefined;
+ resID?: string | undefined;
+ colIndex?: number | undefined;
+ colName?: string | undefined;
+}
+
+export class ProblemPerformanceMetric implements IProblemPerformanceMetric {
+ metric?: string | undefined;
+
+ constructor(data?: IProblemPerformanceMetric) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.metric = data["metric"];
+ }
+ }
+
+ static fromJS(data: any): ProblemPerformanceMetric {
+ data = typeof data === 'object' ? data : {};
+ let result = new ProblemPerformanceMetric();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["metric"] = this.metric;
+ return data;
+ }
+}
+
+export interface IProblemPerformanceMetric {
+ metric?: string | undefined;
+}
+
+export class DarpaDatasetDoc implements IDarpaDatasetDoc {
+ about?: DatasetAbout | undefined;
+ dataResources?: Resource[] | undefined;
+
+ constructor(data?: IDarpaDatasetDoc) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.about = data["about"] ? DatasetAbout.fromJS(data["about"]) : <any>undefined;
+ if (data["dataResources"] && data["dataResources"].constructor === Array) {
+ this.dataResources = [];
+ for (let item of data["dataResources"])
+ this.dataResources.push(Resource.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): DarpaDatasetDoc {
+ data = typeof data === 'object' ? data : {};
+ let result = new DarpaDatasetDoc();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["about"] = this.about ? this.about.toJSON() : <any>undefined;
+ if (this.dataResources && this.dataResources.constructor === Array) {
+ data["dataResources"] = [];
+ for (let item of this.dataResources)
+ data["dataResources"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface IDarpaDatasetDoc {
+ about?: DatasetAbout | undefined;
+ dataResources?: Resource[] | undefined;
+}
+
+export class DatasetAbout implements IDatasetAbout {
+ datasetID?: string | undefined;
+ datasetName?: string | undefined;
+ description?: string | undefined;
+ citation?: string | undefined;
+ license?: string | undefined;
+ source?: string | undefined;
+ sourceURI?: string | undefined;
+ approximateSize?: string | undefined;
+ datasetSchemaVersion?: string | undefined;
+ redacted?: boolean | undefined;
+ datasetVersion?: string | undefined;
+
+ constructor(data?: IDatasetAbout) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.datasetID = data["datasetID"];
+ this.datasetName = data["datasetName"];
+ this.description = data["description"];
+ this.citation = data["citation"];
+ this.license = data["license"];
+ this.source = data["source"];
+ this.sourceURI = data["sourceURI"];
+ this.approximateSize = data["approximateSize"];
+ this.datasetSchemaVersion = data["datasetSchemaVersion"];
+ this.redacted = data["redacted"];
+ this.datasetVersion = data["datasetVersion"];
+ }
+ }
+
+ static fromJS(data: any): DatasetAbout {
+ data = typeof data === 'object' ? data : {};
+ let result = new DatasetAbout();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["datasetID"] = this.datasetID;
+ data["datasetName"] = this.datasetName;
+ data["description"] = this.description;
+ data["citation"] = this.citation;
+ data["license"] = this.license;
+ data["source"] = this.source;
+ data["sourceURI"] = this.sourceURI;
+ data["approximateSize"] = this.approximateSize;
+ data["datasetSchemaVersion"] = this.datasetSchemaVersion;
+ data["redacted"] = this.redacted;
+ data["datasetVersion"] = this.datasetVersion;
+ return data;
+ }
+}
+
+export interface IDatasetAbout {
+ datasetID?: string | undefined;
+ datasetName?: string | undefined;
+ description?: string | undefined;
+ citation?: string | undefined;
+ license?: string | undefined;
+ source?: string | undefined;
+ sourceURI?: string | undefined;
+ approximateSize?: string | undefined;
+ datasetSchemaVersion?: string | undefined;
+ redacted?: boolean | undefined;
+ datasetVersion?: string | undefined;
+}
+
+export class Resource implements IResource {
+ resID?: string | undefined;
+ resPath?: string | undefined;
+ resType?: string | undefined;
+ resFormat?: string[] | undefined;
+ columns?: Column[] | undefined;
+ isCollection?: boolean | undefined;
+
+ constructor(data?: IResource) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.resID = data["resID"];
+ this.resPath = data["resPath"];
+ this.resType = data["resType"];
+ if (data["resFormat"] && data["resFormat"].constructor === Array) {
+ this.resFormat = [];
+ for (let item of data["resFormat"])
+ this.resFormat.push(item);
+ }
+ if (data["columns"] && data["columns"].constructor === Array) {
+ this.columns = [];
+ for (let item of data["columns"])
+ this.columns.push(Column.fromJS(item));
+ }
+ this.isCollection = data["isCollection"];
+ }
+ }
+
+ static fromJS(data: any): Resource {
+ data = typeof data === 'object' ? data : {};
+ let result = new Resource();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["resID"] = this.resID;
+ data["resPath"] = this.resPath;
+ data["resType"] = this.resType;
+ if (this.resFormat && this.resFormat.constructor === Array) {
+ data["resFormat"] = [];
+ for (let item of this.resFormat)
+ data["resFormat"].push(item);
+ }
+ if (this.columns && this.columns.constructor === Array) {
+ data["columns"] = [];
+ for (let item of this.columns)
+ data["columns"].push(item.toJSON());
+ }
+ data["isCollection"] = this.isCollection;
+ return data;
+ }
+}
+
+export interface IResource {
+ resID?: string | undefined;
+ resPath?: string | undefined;
+ resType?: string | undefined;
+ resFormat?: string[] | undefined;
+ columns?: Column[] | undefined;
+ isCollection?: boolean | undefined;
+}
+
+export class Column implements IColumn {
+ colIndex?: number | undefined;
+ colDescription?: string | undefined;
+ colName?: string | undefined;
+ colType?: string | undefined;
+ role?: string[] | undefined;
+ refersTo?: Reference | undefined;
+
+ constructor(data?: IColumn) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.colIndex = data["colIndex"];
+ this.colDescription = data["colDescription"];
+ this.colName = data["colName"];
+ this.colType = data["colType"];
+ if (data["role"] && data["role"].constructor === Array) {
+ this.role = [];
+ for (let item of data["role"])
+ this.role.push(item);
+ }
+ this.refersTo = data["refersTo"] ? Reference.fromJS(data["refersTo"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): Column {
+ data = typeof data === 'object' ? data : {};
+ let result = new Column();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["colIndex"] = this.colIndex;
+ data["colDescription"] = this.colDescription;
+ data["colName"] = this.colName;
+ data["colType"] = this.colType;
+ if (this.role && this.role.constructor === Array) {
+ data["role"] = [];
+ for (let item of this.role)
+ data["role"].push(item);
+ }
+ data["refersTo"] = this.refersTo ? this.refersTo.toJSON() : <any>undefined;
+ return data;
+ }
+}
+
+export interface IColumn {
+ colIndex?: number | undefined;
+ colDescription?: string | undefined;
+ colName?: string | undefined;
+ colType?: string | undefined;
+ role?: string[] | undefined;
+ refersTo?: Reference | undefined;
+}
+
+export class Reference implements IReference {
+ resID?: string | undefined;
+
+ constructor(data?: IReference) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.resID = data["resID"];
+ }
+ }
+
+ static fromJS(data: any): Reference {
+ data = typeof data === 'object' ? data : {};
+ let result = new Reference();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["resID"] = this.resID;
+ return data;
+ }
+}
+
+export interface IReference {
+ resID?: string | undefined;
+}
+
+export class ProblemFinderRows implements IProblemFinderRows {
+ label?: string | undefined;
+ type?: string | undefined;
+ features?: Feature[] | undefined;
+
+ constructor(data?: IProblemFinderRows) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.label = data["label"];
+ this.type = data["type"];
+ if (data["features"] && data["features"].constructor === Array) {
+ this.features = [];
+ for (let item of data["features"])
+ this.features.push(Feature.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): ProblemFinderRows {
+ data = typeof data === 'object' ? data : {};
+ let result = new ProblemFinderRows();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["label"] = this.label;
+ data["type"] = this.type;
+ if (this.features && this.features.constructor === Array) {
+ data["features"] = [];
+ for (let item of this.features)
+ data["features"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface IProblemFinderRows {
+ label?: string | undefined;
+ type?: string | undefined;
+ features?: Feature[] | undefined;
+}
+
+export class Feature implements IFeature {
+ name?: string | undefined;
+ selected?: boolean | undefined;
+ value?: number | undefined;
+
+ constructor(data?: IFeature) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.name = data["name"];
+ this.selected = data["selected"];
+ this.value = data["value"];
+ }
+ }
+
+ static fromJS(data: any): Feature {
+ data = typeof data === 'object' ? data : {};
+ let result = new Feature();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["name"] = this.name;
+ data["selected"] = this.selected;
+ data["value"] = this.value;
+ return data;
+ }
+}
+
+export interface IFeature {
+ name?: string | undefined;
+ selected?: boolean | undefined;
+ value?: number | undefined;
+}
+
+export enum DataType2 {
+ Int = 0,
+ String = 1,
+ Float = 2,
+ Double = 3,
+ DateTime = 4,
+ Object = 5,
+ Undefined = 6,
+}
+
+export abstract class DataTypeExtensions implements IDataTypeExtensions {
+
+ constructor(data?: IDataTypeExtensions) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): DataTypeExtensions {
+ data = typeof data === 'object' ? data : {};
+ throw new Error("The abstract class 'DataTypeExtensions' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ return data;
+ }
+}
+
+export interface IDataTypeExtensions {
+}
+
+export class ResObject implements IResObject {
+ columnName?: string | undefined;
+
+ constructor(data?: IResObject) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.columnName = data["columnName"];
+ }
+ }
+
+ static fromJS(data: any): ResObject {
+ data = typeof data === 'object' ? data : {};
+ let result = new ResObject();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["columnName"] = this.columnName;
+ return data;
+ }
+}
+
+export interface IResObject {
+ columnName?: string | undefined;
+}
+
+export class Exception implements IException {
+ message?: string | undefined;
+ innerException?: Exception | undefined;
+ stackTrace?: string | undefined;
+ source?: string | undefined;
+
+ constructor(data?: IException) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.message = data["Message"];
+ this.innerException = data["InnerException"] ? Exception.fromJS(data["InnerException"]) : <any>undefined;
+ this.stackTrace = data["StackTrace"];
+ this.source = data["Source"];
+ }
+ }
+
+ static fromJS(data: any): Exception {
+ data = typeof data === 'object' ? data : {};
+ let result = new Exception();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Message"] = this.message;
+ data["InnerException"] = this.innerException ? this.innerException.toJSON() : <any>undefined;
+ data["StackTrace"] = this.stackTrace;
+ data["Source"] = this.source;
+ return data;
+ }
+}
+
+export interface IException {
+ message?: string | undefined;
+ innerException?: Exception | undefined;
+ stackTrace?: string | undefined;
+ source?: string | undefined;
+}
+
+export class IDEAException extends Exception implements IIDEAException {
+
+ constructor(data?: IIDEAException) {
+ super(data);
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): IDEAException {
+ data = typeof data === 'object' ? data : {};
+ let result = new IDEAException();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IIDEAException extends IException {
+}
+
+export class CodeParameters implements ICodeParameters {
+ attributeCodeParameters?: AttributeCodeParameters[] | undefined;
+ adapterName?: string | undefined;
+
+ constructor(data?: ICodeParameters) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) {
+ this.attributeCodeParameters = [];
+ for (let item of data["AttributeCodeParameters"])
+ this.attributeCodeParameters.push(AttributeCodeParameters.fromJS(item));
+ }
+ this.adapterName = data["AdapterName"];
+ }
+ }
+
+ static fromJS(data: any): CodeParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new CodeParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) {
+ data["AttributeCodeParameters"] = [];
+ for (let item of this.attributeCodeParameters)
+ data["AttributeCodeParameters"].push(item.toJSON());
+ }
+ data["AdapterName"] = this.adapterName;
+ return data;
+ }
+}
+
+export interface ICodeParameters {
+ attributeCodeParameters?: AttributeCodeParameters[] | undefined;
+ adapterName?: string | undefined;
+}
+
+export class CompileResult implements ICompileResult {
+ compileSuccess?: boolean | undefined;
+ compileMessage?: string | undefined;
+ dataType?: DataType | undefined;
+ replaceAttributeParameters?: AttributeParameters[] | undefined;
+
+ constructor(data?: ICompileResult) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.compileSuccess = data["CompileSuccess"];
+ this.compileMessage = data["CompileMessage"];
+ this.dataType = data["DataType"];
+ if (data["ReplaceAttributeParameters"] && data["ReplaceAttributeParameters"].constructor === Array) {
+ this.replaceAttributeParameters = [];
+ for (let item of data["ReplaceAttributeParameters"])
+ this.replaceAttributeParameters.push(AttributeParameters.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): CompileResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new CompileResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["CompileSuccess"] = this.compileSuccess;
+ data["CompileMessage"] = this.compileMessage;
+ data["DataType"] = this.dataType;
+ if (this.replaceAttributeParameters && this.replaceAttributeParameters.constructor === Array) {
+ data["ReplaceAttributeParameters"] = [];
+ for (let item of this.replaceAttributeParameters)
+ data["ReplaceAttributeParameters"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface ICompileResult {
+ compileSuccess?: boolean | undefined;
+ compileMessage?: string | undefined;
+ dataType?: DataType | undefined;
+ replaceAttributeParameters?: AttributeParameters[] | undefined;
+}
+
+export class CompileResults implements ICompileResults {
+ rawNameToCompileResult?: { [key: string]: CompileResult; } | undefined;
+
+ constructor(data?: ICompileResults) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["RawNameToCompileResult"]) {
+ this.rawNameToCompileResult = {};
+ for (let key in data["RawNameToCompileResult"]) {
+ if (data["RawNameToCompileResult"].hasOwnProperty(key))
+ this.rawNameToCompileResult[key] = data["RawNameToCompileResult"][key] ? CompileResult.fromJS(data["RawNameToCompileResult"][key]) : new CompileResult();
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): CompileResults {
+ data = typeof data === 'object' ? data : {};
+ let result = new CompileResults();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.rawNameToCompileResult) {
+ data["RawNameToCompileResult"] = {};
+ for (let key in this.rawNameToCompileResult) {
+ if (this.rawNameToCompileResult.hasOwnProperty(key))
+ data["RawNameToCompileResult"][key] = this.rawNameToCompileResult[key];
+ }
+ }
+ return data;
+ }
+}
+
+export interface ICompileResults {
+ rawNameToCompileResult?: { [key: string]: CompileResult; } | undefined;
+}
+
+export abstract class UniqueJson implements IUniqueJson {
+
+ constructor(data?: IUniqueJson) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): UniqueJson {
+ data = typeof data === 'object' ? data : {};
+ throw new Error("The abstract class 'UniqueJson' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ return data;
+ }
+}
+
+export interface IUniqueJson {
+}
+
+export abstract class OperationParameters extends UniqueJson implements IOperationParameters {
+ isCachable?: boolean | undefined;
+
+ protected _discriminator: string;
+
+ constructor(data?: IOperationParameters) {
+ super(data);
+ this._discriminator = "OperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.isCachable = data["IsCachable"];
+ }
+ }
+
+ static fromJS(data: any): OperationParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "DataOperationParameters") {
+ throw new Error("The abstract class 'DataOperationParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "ExampleOperationParameters") {
+ let result = new ExampleOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "HistogramOperationParameters") {
+ let result = new HistogramOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DistOperationParameters") {
+ throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "OptimizerOperationParameters") {
+ let result = new OptimizerOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RawDataOperationParameters") {
+ let result = new RawDataOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RecommenderOperationParameters") {
+ let result = new RecommenderOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CDFOperationParameters") {
+ let result = new CDFOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "TestDistOperationParameters") {
+ throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "ChiSquaredTestOperationParameters") {
+ let result = new ChiSquaredTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "HypothesisTestParameters") {
+ throw new Error("The abstract class 'HypothesisTestParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "CorrelationTestOperationParameters") {
+ let result = new CorrelationTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "EmpiricalDistOperationParameters") {
+ let result = new EmpiricalDistOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "KSTestOperationParameters") {
+ let result = new KSTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "NewModelOperationParameters") {
+ let result = new NewModelOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "ModelOperationParameters") {
+ throw new Error("The abstract class 'ModelOperationParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "RootMeanSquareTestOperationParameters") {
+ let result = new RootMeanSquareTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "TTestOperationParameters") {
+ let result = new TTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "FeatureImportanceOperationParameters") {
+ let result = new FeatureImportanceOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SampleOperationParameters") {
+ let result = new SampleOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AddComparisonParameters") {
+ let result = new AddComparisonParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "GetModelStateParameters") {
+ let result = new GetModelStateParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "FrequentItemsetOperationParameters") {
+ let result = new FrequentItemsetOperationParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'OperationParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ data["IsCachable"] = this.isCachable;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IOperationParameters extends IUniqueJson {
+ isCachable?: boolean | undefined;
+}
+
+export abstract class DataOperationParameters extends OperationParameters implements IDataOperationParameters {
+ sampleStreamBlockSize?: number | undefined;
+ adapterName?: string | undefined;
+ isCachable?: boolean | undefined;
+
+ constructor(data?: IDataOperationParameters) {
+ super(data);
+ this._discriminator = "DataOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.sampleStreamBlockSize = data["SampleStreamBlockSize"];
+ this.adapterName = data["AdapterName"];
+ this.isCachable = data["IsCachable"];
+ }
+ }
+
+ static fromJS(data: any): DataOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "ExampleOperationParameters") {
+ let result = new ExampleOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "HistogramOperationParameters") {
+ let result = new HistogramOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DistOperationParameters") {
+ throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "OptimizerOperationParameters") {
+ let result = new OptimizerOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RawDataOperationParameters") {
+ let result = new RawDataOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RecommenderOperationParameters") {
+ let result = new RecommenderOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CDFOperationParameters") {
+ let result = new CDFOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "TestDistOperationParameters") {
+ throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "EmpiricalDistOperationParameters") {
+ let result = new EmpiricalDistOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "FeatureImportanceOperationParameters") {
+ let result = new FeatureImportanceOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SampleOperationParameters") {
+ let result = new SampleOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "FrequentItemsetOperationParameters") {
+ let result = new FrequentItemsetOperationParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'DataOperationParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["SampleStreamBlockSize"] = this.sampleStreamBlockSize;
+ data["AdapterName"] = this.adapterName;
+ data["IsCachable"] = this.isCachable;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDataOperationParameters extends IOperationParameters {
+ sampleStreamBlockSize?: number | undefined;
+ adapterName?: string | undefined;
+ isCachable?: boolean | undefined;
+}
+
+export class ExampleOperationParameters extends DataOperationParameters implements IExampleOperationParameters {
+ filter?: string | undefined;
+ attributeParameters?: AttributeParameters[] | undefined;
+ attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined;
+ dummyValue?: number | undefined;
+ exampleType?: string | undefined;
+
+ constructor(data?: IExampleOperationParameters) {
+ super(data);
+ this._discriminator = "ExampleOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.filter = data["Filter"];
+ if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) {
+ this.attributeParameters = [];
+ for (let item of data["AttributeParameters"])
+ this.attributeParameters.push(AttributeParameters.fromJS(item));
+ }
+ if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) {
+ this.attributeCodeParameters = [];
+ for (let item of data["AttributeCodeParameters"])
+ this.attributeCodeParameters.push(AttributeCaclculatedParameters.fromJS(item));
+ }
+ this.dummyValue = data["DummyValue"];
+ this.exampleType = data["ExampleType"];
+ }
+ }
+
+ static fromJS(data: any): ExampleOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new ExampleOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Filter"] = this.filter;
+ if (this.attributeParameters && this.attributeParameters.constructor === Array) {
+ data["AttributeParameters"] = [];
+ for (let item of this.attributeParameters)
+ data["AttributeParameters"].push(item.toJSON());
+ }
+ if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) {
+ data["AttributeCodeParameters"] = [];
+ for (let item of this.attributeCodeParameters)
+ data["AttributeCodeParameters"].push(item.toJSON());
+ }
+ data["DummyValue"] = this.dummyValue;
+ data["ExampleType"] = this.exampleType;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IExampleOperationParameters extends IDataOperationParameters {
+ filter?: string | undefined;
+ attributeParameters?: AttributeParameters[] | undefined;
+ attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined;
+ dummyValue?: number | undefined;
+ exampleType?: string | undefined;
+}
+
+export abstract class DistOperationParameters extends DataOperationParameters implements IDistOperationParameters {
+ filter?: string | undefined;
+ attributeCalculatedParameters?: AttributeCaclculatedParameters[] | undefined;
+
+ constructor(data?: IDistOperationParameters) {
+ super(data);
+ this._discriminator = "DistOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.filter = data["Filter"];
+ if (data["AttributeCalculatedParameters"] && data["AttributeCalculatedParameters"].constructor === Array) {
+ this.attributeCalculatedParameters = [];
+ for (let item of data["AttributeCalculatedParameters"])
+ this.attributeCalculatedParameters.push(AttributeCaclculatedParameters.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): DistOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "HistogramOperationParameters") {
+ let result = new HistogramOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "OptimizerOperationParameters") {
+ let result = new OptimizerOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RawDataOperationParameters") {
+ let result = new RawDataOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CDFOperationParameters") {
+ let result = new CDFOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "TestDistOperationParameters") {
+ throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "EmpiricalDistOperationParameters") {
+ let result = new EmpiricalDistOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "FeatureImportanceOperationParameters") {
+ let result = new FeatureImportanceOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SampleOperationParameters") {
+ let result = new SampleOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "FrequentItemsetOperationParameters") {
+ let result = new FrequentItemsetOperationParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'DistOperationParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Filter"] = this.filter;
+ if (this.attributeCalculatedParameters && this.attributeCalculatedParameters.constructor === Array) {
+ data["AttributeCalculatedParameters"] = [];
+ for (let item of this.attributeCalculatedParameters)
+ data["AttributeCalculatedParameters"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDistOperationParameters extends IDataOperationParameters {
+ filter?: string | undefined;
+ attributeCalculatedParameters?: AttributeCaclculatedParameters[] | undefined;
+}
+
+export class HistogramOperationParameters extends DistOperationParameters implements IHistogramOperationParameters {
+ sortPerBinAggregateParameter?: AggregateParameters | undefined;
+ binningParameters?: BinningParameters[] | undefined;
+ perBinAggregateParameters?: AggregateParameters[] | undefined;
+ globalAggregateParameters?: AggregateParameters[] | undefined;
+ brushes?: string[] | undefined;
+ enableBrushComputation?: boolean | undefined;
+ degreeOfParallism?: number | undefined;
+
+ constructor(data?: IHistogramOperationParameters) {
+ super(data);
+ this._discriminator = "HistogramOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.sortPerBinAggregateParameter = data["SortPerBinAggregateParameter"] ? AggregateParameters.fromJS(data["SortPerBinAggregateParameter"]) : <any>undefined;
+ if (data["BinningParameters"] && data["BinningParameters"].constructor === Array) {
+ this.binningParameters = [];
+ for (let item of data["BinningParameters"])
+ this.binningParameters.push(BinningParameters.fromJS(item));
+ }
+ if (data["PerBinAggregateParameters"] && data["PerBinAggregateParameters"].constructor === Array) {
+ this.perBinAggregateParameters = [];
+ for (let item of data["PerBinAggregateParameters"])
+ this.perBinAggregateParameters.push(AggregateParameters.fromJS(item));
+ }
+ if (data["GlobalAggregateParameters"] && data["GlobalAggregateParameters"].constructor === Array) {
+ this.globalAggregateParameters = [];
+ for (let item of data["GlobalAggregateParameters"])
+ this.globalAggregateParameters.push(AggregateParameters.fromJS(item));
+ }
+ if (data["Brushes"] && data["Brushes"].constructor === Array) {
+ this.brushes = [];
+ for (let item of data["Brushes"])
+ this.brushes.push(item);
+ }
+ this.enableBrushComputation = data["EnableBrushComputation"];
+ this.degreeOfParallism = data["DegreeOfParallism"];
+ }
+ }
+
+ static fromJS(data: any): HistogramOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new HistogramOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["SortPerBinAggregateParameter"] = this.sortPerBinAggregateParameter ? this.sortPerBinAggregateParameter.toJSON() : <any>undefined;
+ if (this.binningParameters && this.binningParameters.constructor === Array) {
+ data["BinningParameters"] = [];
+ for (let item of this.binningParameters)
+ data["BinningParameters"].push(item.toJSON());
+ }
+ if (this.perBinAggregateParameters && this.perBinAggregateParameters.constructor === Array) {
+ data["PerBinAggregateParameters"] = [];
+ for (let item of this.perBinAggregateParameters)
+ data["PerBinAggregateParameters"].push(item.toJSON());
+ }
+ if (this.globalAggregateParameters && this.globalAggregateParameters.constructor === Array) {
+ data["GlobalAggregateParameters"] = [];
+ for (let item of this.globalAggregateParameters)
+ data["GlobalAggregateParameters"].push(item.toJSON());
+ }
+ if (this.brushes && this.brushes.constructor === Array) {
+ data["Brushes"] = [];
+ for (let item of this.brushes)
+ data["Brushes"].push(item);
+ }
+ data["EnableBrushComputation"] = this.enableBrushComputation;
+ data["DegreeOfParallism"] = this.degreeOfParallism;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IHistogramOperationParameters extends IDistOperationParameters {
+ sortPerBinAggregateParameter?: AggregateParameters | undefined;
+ binningParameters?: BinningParameters[] | undefined;
+ perBinAggregateParameters?: AggregateParameters[] | undefined;
+ globalAggregateParameters?: AggregateParameters[] | undefined;
+ brushes?: string[] | undefined;
+ enableBrushComputation?: boolean | undefined;
+ degreeOfParallism?: number | undefined;
+}
+
+export class OptimizerOperationParameters extends DistOperationParameters implements IOptimizerOperationParameters {
+ taskType?: TaskType | undefined;
+ taskSubType?: TaskSubType | undefined;
+ metricType?: MetricType | undefined;
+ labelAttribute?: AttributeParameters | undefined;
+ featureAttributes?: AttributeParameters[] | undefined;
+ requiredPrimitives?: string[] | undefined;
+ pythonFilter?: string | undefined;
+ trainFilter?: string | undefined;
+ pythonTrainFilter?: string | undefined;
+ testFilter?: string | undefined;
+ pythonTestFilter?: string | undefined;
+
+ constructor(data?: IOptimizerOperationParameters) {
+ super(data);
+ this._discriminator = "OptimizerOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.taskType = data["TaskType"];
+ this.taskSubType = data["TaskSubType"];
+ this.metricType = data["MetricType"];
+ this.labelAttribute = data["LabelAttribute"] ? AttributeParameters.fromJS(data["LabelAttribute"]) : <any>undefined;
+ if (data["FeatureAttributes"] && data["FeatureAttributes"].constructor === Array) {
+ this.featureAttributes = [];
+ for (let item of data["FeatureAttributes"])
+ this.featureAttributes.push(AttributeParameters.fromJS(item));
+ }
+ if (data["RequiredPrimitives"] && data["RequiredPrimitives"].constructor === Array) {
+ this.requiredPrimitives = [];
+ for (let item of data["RequiredPrimitives"])
+ this.requiredPrimitives.push(item);
+ }
+ this.pythonFilter = data["PythonFilter"];
+ this.trainFilter = data["TrainFilter"];
+ this.pythonTrainFilter = data["PythonTrainFilter"];
+ this.testFilter = data["TestFilter"];
+ this.pythonTestFilter = data["PythonTestFilter"];
+ }
+ }
+
+ static fromJS(data: any): OptimizerOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new OptimizerOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["TaskType"] = this.taskType;
+ data["TaskSubType"] = this.taskSubType;
+ data["MetricType"] = this.metricType;
+ data["LabelAttribute"] = this.labelAttribute ? this.labelAttribute.toJSON() : <any>undefined;
+ if (this.featureAttributes && this.featureAttributes.constructor === Array) {
+ data["FeatureAttributes"] = [];
+ for (let item of this.featureAttributes)
+ data["FeatureAttributes"].push(item.toJSON());
+ }
+ if (this.requiredPrimitives && this.requiredPrimitives.constructor === Array) {
+ data["RequiredPrimitives"] = [];
+ for (let item of this.requiredPrimitives)
+ data["RequiredPrimitives"].push(item);
+ }
+ data["PythonFilter"] = this.pythonFilter;
+ data["TrainFilter"] = this.trainFilter;
+ data["PythonTrainFilter"] = this.pythonTrainFilter;
+ data["TestFilter"] = this.testFilter;
+ data["PythonTestFilter"] = this.pythonTestFilter;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IOptimizerOperationParameters extends IDistOperationParameters {
+ taskType?: TaskType | undefined;
+ taskSubType?: TaskSubType | undefined;
+ metricType?: MetricType | undefined;
+ labelAttribute?: AttributeParameters | undefined;
+ featureAttributes?: AttributeParameters[] | undefined;
+ requiredPrimitives?: string[] | undefined;
+ pythonFilter?: string | undefined;
+ trainFilter?: string | undefined;
+ pythonTrainFilter?: string | undefined;
+ testFilter?: string | undefined;
+ pythonTestFilter?: string | undefined;
+}
+
+export enum TaskType {
+ Undefined = 0,
+ Classification = 1,
+ Regression = 2,
+ Clustering = 3,
+ LinkPrediction = 4,
+ VertexNomination = 5,
+ CommunityDetection = 6,
+ GraphClustering = 7,
+ GraphMatching = 8,
+ TimeSeriesForecasting = 9,
+ CollaborativeFiltering = 10,
+}
+
+export enum TaskSubType {
+ Undefined = 0,
+ None = 1,
+ Binary = 2,
+ Multiclass = 3,
+ Multilabel = 4,
+ Univariate = 5,
+ Multivariate = 6,
+ Overlapping = 7,
+ Nonoverlapping = 8,
+}
+
+export enum MetricType {
+ MetricUndefined = 0,
+ Accuracy = 1,
+ Precision = 2,
+ Recall = 3,
+ F1 = 4,
+ F1Micro = 5,
+ F1Macro = 6,
+ RocAuc = 7,
+ RocAucMicro = 8,
+ RocAucMacro = 9,
+ MeanSquaredError = 10,
+ RootMeanSquaredError = 11,
+ RootMeanSquaredErrorAvg = 12,
+ MeanAbsoluteError = 13,
+ RSquared = 14,
+ NormalizedMutualInformation = 15,
+ JaccardSimilarityScore = 16,
+ PrecisionAtTopK = 17,
+ ObjectDetectionAveragePrecision = 18,
+ Loss = 100,
+}
+
+export class RawDataOperationParameters extends DistOperationParameters implements IRawDataOperationParameters {
+ sortUpRawName?: string | undefined;
+ sortDownRawName?: string | undefined;
+ numRecords?: number | undefined;
+ binningParameters?: BinningParameters[] | undefined;
+ brushes?: string[] | undefined;
+ enableBrushComputation?: boolean | undefined;
+
+ constructor(data?: IRawDataOperationParameters) {
+ super(data);
+ this._discriminator = "RawDataOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.sortUpRawName = data["SortUpRawName"];
+ this.sortDownRawName = data["SortDownRawName"];
+ this.numRecords = data["NumRecords"];
+ if (data["BinningParameters"] && data["BinningParameters"].constructor === Array) {
+ this.binningParameters = [];
+ for (let item of data["BinningParameters"])
+ this.binningParameters.push(BinningParameters.fromJS(item));
+ }
+ if (data["Brushes"] && data["Brushes"].constructor === Array) {
+ this.brushes = [];
+ for (let item of data["Brushes"])
+ this.brushes.push(item);
+ }
+ this.enableBrushComputation = data["EnableBrushComputation"];
+ }
+ }
+
+ static fromJS(data: any): RawDataOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new RawDataOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["SortUpRawName"] = this.sortUpRawName;
+ data["SortDownRawName"] = this.sortDownRawName;
+ data["NumRecords"] = this.numRecords;
+ if (this.binningParameters && this.binningParameters.constructor === Array) {
+ data["BinningParameters"] = [];
+ for (let item of this.binningParameters)
+ data["BinningParameters"].push(item.toJSON());
+ }
+ if (this.brushes && this.brushes.constructor === Array) {
+ data["Brushes"] = [];
+ for (let item of this.brushes)
+ data["Brushes"].push(item);
+ }
+ data["EnableBrushComputation"] = this.enableBrushComputation;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IRawDataOperationParameters extends IDistOperationParameters {
+ sortUpRawName?: string | undefined;
+ sortDownRawName?: string | undefined;
+ numRecords?: number | undefined;
+ binningParameters?: BinningParameters[] | undefined;
+ brushes?: string[] | undefined;
+ enableBrushComputation?: boolean | undefined;
+}
+
+export class RecommenderOperationParameters extends DataOperationParameters implements IRecommenderOperationParameters {
+ target?: HistogramOperationParameters | undefined;
+ budget?: number | undefined;
+ modelId?: ModelId | undefined;
+ includeAttributeParameters?: AttributeParameters[] | undefined;
+ excludeAttributeParameters?: AttributeParameters[] | undefined;
+ riskControlType?: RiskControlType | undefined;
+
+ constructor(data?: IRecommenderOperationParameters) {
+ super(data);
+ this._discriminator = "RecommenderOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.target = data["Target"] ? HistogramOperationParameters.fromJS(data["Target"]) : <any>undefined;
+ this.budget = data["Budget"];
+ this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
+ if (data["IncludeAttributeParameters"] && data["IncludeAttributeParameters"].constructor === Array) {
+ this.includeAttributeParameters = [];
+ for (let item of data["IncludeAttributeParameters"])
+ this.includeAttributeParameters.push(AttributeParameters.fromJS(item));
+ }
+ if (data["ExcludeAttributeParameters"] && data["ExcludeAttributeParameters"].constructor === Array) {
+ this.excludeAttributeParameters = [];
+ for (let item of data["ExcludeAttributeParameters"])
+ this.excludeAttributeParameters.push(AttributeParameters.fromJS(item));
+ }
+ this.riskControlType = data["RiskControlType"];
+ }
+ }
+
+ static fromJS(data: any): RecommenderOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new RecommenderOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Target"] = this.target ? this.target.toJSON() : <any>undefined;
+ data["Budget"] = this.budget;
+ data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
+ if (this.includeAttributeParameters && this.includeAttributeParameters.constructor === Array) {
+ data["IncludeAttributeParameters"] = [];
+ for (let item of this.includeAttributeParameters)
+ data["IncludeAttributeParameters"].push(item.toJSON());
+ }
+ if (this.excludeAttributeParameters && this.excludeAttributeParameters.constructor === Array) {
+ data["ExcludeAttributeParameters"] = [];
+ for (let item of this.excludeAttributeParameters)
+ data["ExcludeAttributeParameters"].push(item.toJSON());
+ }
+ data["RiskControlType"] = this.riskControlType;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IRecommenderOperationParameters extends IDataOperationParameters {
+ target?: HistogramOperationParameters | undefined;
+ budget?: number | undefined;
+ modelId?: ModelId | undefined;
+ includeAttributeParameters?: AttributeParameters[] | undefined;
+ excludeAttributeParameters?: AttributeParameters[] | undefined;
+ riskControlType?: RiskControlType | undefined;
+}
+
+export class ModelId implements IModelId {
+ value?: string | undefined;
+
+ constructor(data?: IModelId) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): ModelId {
+ data = typeof data === 'object' ? data : {};
+ let result = new ModelId();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ return data;
+ }
+}
+
+export interface IModelId {
+ value?: string | undefined;
+}
+
+export enum RiskControlType {
+ PCER = 0,
+ Bonferroni = 1,
+ AdaBonferroni = 2,
+ HolmBonferroni = 3,
+ BHFDR = 4,
+ SeqFDR = 5,
+ AlphaFDR = 6,
+ BestFootForward = 7,
+ BetaFarsighted = 8,
+ BetaFarsightedWithSupport = 9,
+ GammaFixed = 10,
+ DeltaHopeful = 11,
+ EpsilonHybrid = 12,
+ EpsilonHybridWithoutSupport = 13,
+ PsiSupport = 14,
+ ZetaDynamic = 15,
+ Unknown = 16,
+}
+
+export abstract class TestDistOperationParameters extends DistOperationParameters implements ITestDistOperationParameters {
+ attributeParameters?: AttributeParameters[] | undefined;
+
+ constructor(data?: ITestDistOperationParameters) {
+ super(data);
+ this._discriminator = "TestDistOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) {
+ this.attributeParameters = [];
+ for (let item of data["AttributeParameters"])
+ this.attributeParameters.push(AttributeParameters.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): TestDistOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "CDFOperationParameters") {
+ let result = new CDFOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "EmpiricalDistOperationParameters") {
+ let result = new EmpiricalDistOperationParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'TestDistOperationParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.attributeParameters && this.attributeParameters.constructor === Array) {
+ data["AttributeParameters"] = [];
+ for (let item of this.attributeParameters)
+ data["AttributeParameters"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ITestDistOperationParameters extends IDistOperationParameters {
+ attributeParameters?: AttributeParameters[] | undefined;
+}
+
+export class CDFOperationParameters extends TestDistOperationParameters implements ICDFOperationParameters {
+
+ constructor(data?: ICDFOperationParameters) {
+ super(data);
+ this._discriminator = "CDFOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): CDFOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new CDFOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ICDFOperationParameters extends ITestDistOperationParameters {
+}
+
+export abstract class HypothesisTestParameters extends OperationParameters implements IHypothesisTestParameters {
+ childOperationParameters?: OperationParameters[] | undefined;
+ isCachable?: boolean | undefined;
+
+ constructor(data?: IHypothesisTestParameters) {
+ super(data);
+ this._discriminator = "HypothesisTestParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["ChildOperationParameters"] && data["ChildOperationParameters"].constructor === Array) {
+ this.childOperationParameters = [];
+ for (let item of data["ChildOperationParameters"])
+ this.childOperationParameters.push(OperationParameters.fromJS(item));
+ }
+ this.isCachable = data["IsCachable"];
+ }
+ }
+
+ static fromJS(data: any): HypothesisTestParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "ChiSquaredTestOperationParameters") {
+ let result = new ChiSquaredTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CorrelationTestOperationParameters") {
+ let result = new CorrelationTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "KSTestOperationParameters") {
+ let result = new KSTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RootMeanSquareTestOperationParameters") {
+ let result = new RootMeanSquareTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "TTestOperationParameters") {
+ let result = new TTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'HypothesisTestParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.childOperationParameters && this.childOperationParameters.constructor === Array) {
+ data["ChildOperationParameters"] = [];
+ for (let item of this.childOperationParameters)
+ data["ChildOperationParameters"].push(item.toJSON());
+ }
+ data["IsCachable"] = this.isCachable;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IHypothesisTestParameters extends IOperationParameters {
+ childOperationParameters?: OperationParameters[] | undefined;
+ isCachable?: boolean | undefined;
+}
+
+export class ChiSquaredTestOperationParameters extends HypothesisTestParameters implements IChiSquaredTestOperationParameters {
+
+ constructor(data?: IChiSquaredTestOperationParameters) {
+ super(data);
+ this._discriminator = "ChiSquaredTestOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): ChiSquaredTestOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new ChiSquaredTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IChiSquaredTestOperationParameters extends IHypothesisTestParameters {
+}
+
+export class CorrelationTestOperationParameters extends HypothesisTestParameters implements ICorrelationTestOperationParameters {
+
+ constructor(data?: ICorrelationTestOperationParameters) {
+ super(data);
+ this._discriminator = "CorrelationTestOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): CorrelationTestOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new CorrelationTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ICorrelationTestOperationParameters extends IHypothesisTestParameters {
+}
+
+export class SubmitProblemParameters implements ISubmitProblemParameters {
+ id?: string | undefined;
+
+ constructor(data?: ISubmitProblemParameters) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.id = data["Id"];
+ }
+ }
+
+ static fromJS(data: any): SubmitProblemParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new SubmitProblemParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Id"] = this.id;
+ return data;
+ }
+}
+
+export interface ISubmitProblemParameters {
+ id?: string | undefined;
+}
+
+export class SpecifyProblemParameters implements ISpecifyProblemParameters {
+ id?: string | undefined;
+ userComment?: string | undefined;
+ optimizerOperationParameters?: OptimizerOperationParameters | undefined;
+
+ constructor(data?: ISpecifyProblemParameters) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.id = data["Id"];
+ this.userComment = data["UserComment"];
+ this.optimizerOperationParameters = data["OptimizerOperationParameters"] ? OptimizerOperationParameters.fromJS(data["OptimizerOperationParameters"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): SpecifyProblemParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new SpecifyProblemParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Id"] = this.id;
+ data["UserComment"] = this.userComment;
+ data["OptimizerOperationParameters"] = this.optimizerOperationParameters ? this.optimizerOperationParameters.toJSON() : <any>undefined;
+ return data;
+ }
+}
+
+export interface ISpecifyProblemParameters {
+ id?: string | undefined;
+ userComment?: string | undefined;
+ optimizerOperationParameters?: OptimizerOperationParameters | undefined;
+}
+
+export class EmpiricalDistOperationParameters extends TestDistOperationParameters implements IEmpiricalDistOperationParameters {
+ keepSamples?: boolean | undefined;
+
+ constructor(data?: IEmpiricalDistOperationParameters) {
+ super(data);
+ this._discriminator = "EmpiricalDistOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.keepSamples = data["KeepSamples"];
+ }
+ }
+
+ static fromJS(data: any): EmpiricalDistOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new EmpiricalDistOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["KeepSamples"] = this.keepSamples;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IEmpiricalDistOperationParameters extends ITestDistOperationParameters {
+ keepSamples?: boolean | undefined;
+}
+
+export class KSTestOperationParameters extends HypothesisTestParameters implements IKSTestOperationParameters {
+
+ constructor(data?: IKSTestOperationParameters) {
+ super(data);
+ this._discriminator = "KSTestOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): KSTestOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new KSTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IKSTestOperationParameters extends IHypothesisTestParameters {
+}
+
+export abstract class ModelOperationParameters extends OperationParameters implements IModelOperationParameters {
+
+ constructor(data?: IModelOperationParameters) {
+ super(data);
+ this._discriminator = "ModelOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): ModelOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "NewModelOperationParameters") {
+ let result = new NewModelOperationParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AddComparisonParameters") {
+ let result = new AddComparisonParameters();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "GetModelStateParameters") {
+ let result = new GetModelStateParameters();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'ModelOperationParameters' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IModelOperationParameters extends IOperationParameters {
+}
+
+export class NewModelOperationParameters extends ModelOperationParameters implements INewModelOperationParameters {
+ riskControlTypes?: RiskControlType[] | undefined;
+ alpha?: number | undefined;
+ alphaInvestParameter?: AlphaInvestParameter | undefined;
+
+ constructor(data?: INewModelOperationParameters) {
+ super(data);
+ this._discriminator = "NewModelOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["RiskControlTypes"] && data["RiskControlTypes"].constructor === Array) {
+ this.riskControlTypes = [];
+ for (let item of data["RiskControlTypes"])
+ this.riskControlTypes.push(item);
+ }
+ this.alpha = data["Alpha"];
+ this.alphaInvestParameter = data["AlphaInvestParameter"] ? AlphaInvestParameter.fromJS(data["AlphaInvestParameter"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): NewModelOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new NewModelOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.riskControlTypes && this.riskControlTypes.constructor === Array) {
+ data["RiskControlTypes"] = [];
+ for (let item of this.riskControlTypes)
+ data["RiskControlTypes"].push(item);
+ }
+ data["Alpha"] = this.alpha;
+ data["AlphaInvestParameter"] = this.alphaInvestParameter ? this.alphaInvestParameter.toJSON() : <any>undefined;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface INewModelOperationParameters extends IModelOperationParameters {
+ riskControlTypes?: RiskControlType[] | undefined;
+ alpha?: number | undefined;
+ alphaInvestParameter?: AlphaInvestParameter | undefined;
+}
+
+export class AlphaInvestParameter extends UniqueJson implements IAlphaInvestParameter {
+ beta?: number | undefined;
+ gamma?: number | undefined;
+ delta?: number | undefined;
+ epsilon?: number | undefined;
+ windowSize?: number | undefined;
+ psi?: number | undefined;
+
+ constructor(data?: IAlphaInvestParameter) {
+ super(data);
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.beta = data["Beta"];
+ this.gamma = data["Gamma"];
+ this.delta = data["Delta"];
+ this.epsilon = data["Epsilon"];
+ this.windowSize = data["WindowSize"];
+ this.psi = data["Psi"];
+ }
+ }
+
+ static fromJS(data: any): AlphaInvestParameter {
+ data = typeof data === 'object' ? data : {};
+ let result = new AlphaInvestParameter();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Beta"] = this.beta;
+ data["Gamma"] = this.gamma;
+ data["Delta"] = this.delta;
+ data["Epsilon"] = this.epsilon;
+ data["WindowSize"] = this.windowSize;
+ data["Psi"] = this.psi;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAlphaInvestParameter extends IUniqueJson {
+ beta?: number | undefined;
+ gamma?: number | undefined;
+ delta?: number | undefined;
+ epsilon?: number | undefined;
+ windowSize?: number | undefined;
+ psi?: number | undefined;
+}
+
+export class RootMeanSquareTestOperationParameters extends HypothesisTestParameters implements IRootMeanSquareTestOperationParameters {
+ seeded?: boolean | undefined;
+ pValueConvergenceThreshold?: number | undefined;
+ maxSimulationBatchCount?: number | undefined;
+ perBatchSimulationCount?: number | undefined;
+
+ constructor(data?: IRootMeanSquareTestOperationParameters) {
+ super(data);
+ this._discriminator = "RootMeanSquareTestOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.seeded = data["Seeded"];
+ this.pValueConvergenceThreshold = data["PValueConvergenceThreshold"];
+ this.maxSimulationBatchCount = data["MaxSimulationBatchCount"];
+ this.perBatchSimulationCount = data["PerBatchSimulationCount"];
+ }
+ }
+
+ static fromJS(data: any): RootMeanSquareTestOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new RootMeanSquareTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Seeded"] = this.seeded;
+ data["PValueConvergenceThreshold"] = this.pValueConvergenceThreshold;
+ data["MaxSimulationBatchCount"] = this.maxSimulationBatchCount;
+ data["PerBatchSimulationCount"] = this.perBatchSimulationCount;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IRootMeanSquareTestOperationParameters extends IHypothesisTestParameters {
+ seeded?: boolean | undefined;
+ pValueConvergenceThreshold?: number | undefined;
+ maxSimulationBatchCount?: number | undefined;
+ perBatchSimulationCount?: number | undefined;
+}
+
+export class TTestOperationParameters extends HypothesisTestParameters implements ITTestOperationParameters {
+
+ constructor(data?: ITTestOperationParameters) {
+ super(data);
+ this._discriminator = "TTestOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): TTestOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new TTestOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ITTestOperationParameters extends IHypothesisTestParameters {
+}
+
+export enum EffectSize {
+ Small = 1,
+ Meduim = 2,
+ Large = 4,
+}
+
+export abstract class Result extends UniqueJson implements IResult {
+ progress?: number | undefined;
+
+ protected _discriminator: string;
+
+ constructor(data?: IResult) {
+ super(data);
+ this._discriminator = "Result";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.progress = data["Progress"];
+ }
+ }
+
+ static fromJS(data: any): Result {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "ErrorResult") {
+ let result = new ErrorResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "HistogramResult") {
+ let result = new HistogramResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DistResult") {
+ throw new Error("The abstract class 'DistResult' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "ModelWealthResult") {
+ let result = new ModelWealthResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "HypothesisTestResult") {
+ throw new Error("The abstract class 'HypothesisTestResult' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "ModelOperationResult") {
+ throw new Error("The abstract class 'ModelOperationResult' cannot be instantiated.");
+ }
+ if (data["discriminator"] === "RecommenderResult") {
+ let result = new RecommenderResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "Decision") {
+ let result = new Decision();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "OptimizerResult") {
+ let result = new OptimizerResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "ExampleResult") {
+ let result = new ExampleResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "NewModelOperationResult") {
+ let result = new NewModelOperationResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AddComparisonResult") {
+ let result = new AddComparisonResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "GetModelStateResult") {
+ let result = new GetModelStateResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "FeatureImportanceResult") {
+ let result = new FeatureImportanceResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RawDataResult") {
+ let result = new RawDataResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SampleResult") {
+ let result = new SampleResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CDFResult") {
+ let result = new CDFResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "ChiSquaredTestResult") {
+ let result = new ChiSquaredTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CorrelationTestResult") {
+ let result = new CorrelationTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "EmpiricalDistResult") {
+ let result = new EmpiricalDistResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "KSTestResult") {
+ let result = new KSTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RootMeanSquareTestResult") {
+ let result = new RootMeanSquareTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "TTestResult") {
+ let result = new TTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "FrequentItemsetResult") {
+ let result = new FrequentItemsetResult();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'Result' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ data["Progress"] = this.progress;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IResult extends IUniqueJson {
+ progress?: number | undefined;
+}
+
+export class ErrorResult extends Result implements IErrorResult {
+ message?: string | undefined;
+
+ constructor(data?: IErrorResult) {
+ super(data);
+ this._discriminator = "ErrorResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.message = data["Message"];
+ }
+ }
+
+ static fromJS(data: any): ErrorResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new ErrorResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Message"] = this.message;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IErrorResult extends IResult {
+ message?: string | undefined;
+}
+
+export abstract class DistResult extends Result implements IDistResult {
+ sampleSize?: number | undefined;
+ populationSize?: number | undefined;
+
+ constructor(data?: IDistResult) {
+ super(data);
+ this._discriminator = "DistResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.sampleSize = data["SampleSize"];
+ this.populationSize = data["PopulationSize"];
+ }
+ }
+
+ static fromJS(data: any): DistResult {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "HistogramResult") {
+ let result = new HistogramResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RawDataResult") {
+ let result = new RawDataResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SampleResult") {
+ let result = new SampleResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CDFResult") {
+ let result = new CDFResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "EmpiricalDistResult") {
+ let result = new EmpiricalDistResult();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'DistResult' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["SampleSize"] = this.sampleSize;
+ data["PopulationSize"] = this.populationSize;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDistResult extends IResult {
+ sampleSize?: number | undefined;
+ populationSize?: number | undefined;
+}
+
+export class HistogramResult extends DistResult implements IHistogramResult {
+ aggregateResults?: AggregateResult[][] | undefined;
+ isEmpty?: boolean | undefined;
+ brushes?: Brush[] | undefined;
+ binRanges?: BinRange[] | undefined;
+ aggregateParameters?: AggregateParameters[] | undefined;
+ nullValueCount?: number | undefined;
+ bins?: { [key: string]: Bin; } | undefined;
+
+ constructor(data?: IHistogramResult) {
+ super(data);
+ this._discriminator = "HistogramResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["AggregateResults"] && data["AggregateResults"].constructor === Array) {
+ this.aggregateResults = [];
+ for (let item of data["AggregateResults"])
+ this.aggregateResults.push(item);
+ }
+ this.isEmpty = data["IsEmpty"];
+ if (data["Brushes"] && data["Brushes"].constructor === Array) {
+ this.brushes = [];
+ for (let item of data["Brushes"])
+ this.brushes.push(Brush.fromJS(item));
+ }
+ if (data["BinRanges"] && data["BinRanges"].constructor === Array) {
+ this.binRanges = [];
+ for (let item of data["BinRanges"])
+ this.binRanges.push(BinRange.fromJS(item));
+ }
+ if (data["AggregateParameters"] && data["AggregateParameters"].constructor === Array) {
+ this.aggregateParameters = [];
+ for (let item of data["AggregateParameters"])
+ this.aggregateParameters.push(AggregateParameters.fromJS(item));
+ }
+ this.nullValueCount = data["NullValueCount"];
+ if (data["Bins"]) {
+ this.bins = {};
+ for (let key in data["Bins"]) {
+ if (data["Bins"].hasOwnProperty(key))
+ this.bins[key] = data["Bins"][key] ? Bin.fromJS(data["Bins"][key]) : new Bin();
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): HistogramResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new HistogramResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.aggregateResults && this.aggregateResults.constructor === Array) {
+ data["AggregateResults"] = [];
+ for (let item of this.aggregateResults)
+ data["AggregateResults"].push(item);
+ }
+ data["IsEmpty"] = this.isEmpty;
+ if (this.brushes && this.brushes.constructor === Array) {
+ data["Brushes"] = [];
+ for (let item of this.brushes)
+ data["Brushes"].push(item.toJSON());
+ }
+ if (this.binRanges && this.binRanges.constructor === Array) {
+ data["BinRanges"] = [];
+ for (let item of this.binRanges)
+ data["BinRanges"].push(item.toJSON());
+ }
+ if (this.aggregateParameters && this.aggregateParameters.constructor === Array) {
+ data["AggregateParameters"] = [];
+ for (let item of this.aggregateParameters)
+ data["AggregateParameters"].push(item.toJSON());
+ }
+ data["NullValueCount"] = this.nullValueCount;
+ if (this.bins) {
+ data["Bins"] = {};
+ for (let key in this.bins) {
+ if (this.bins.hasOwnProperty(key))
+ data["Bins"][key] = this.bins[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IHistogramResult extends IDistResult {
+ aggregateResults?: AggregateResult[][] | undefined;
+ isEmpty?: boolean | undefined;
+ brushes?: Brush[] | undefined;
+ binRanges?: BinRange[] | undefined;
+ aggregateParameters?: AggregateParameters[] | undefined;
+ nullValueCount?: number | undefined;
+ bins?: { [key: string]: Bin; } | undefined;
+}
+
+export abstract class AggregateResult implements IAggregateResult {
+ hasResult?: boolean | undefined;
+ n?: number | undefined;
+
+ protected _discriminator: string;
+
+ constructor(data?: IAggregateResult) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "AggregateResult";
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.hasResult = data["HasResult"];
+ this.n = data["N"];
+ }
+ }
+
+ static fromJS(data: any): AggregateResult | undefined {
+ if (data === null || data === undefined) {
+ return undefined;
+ }
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "MarginAggregateResult") {
+ let result = new MarginAggregateResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DoubleValueAggregateResult") {
+ let result = new DoubleValueAggregateResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "PointsAggregateResult") {
+ let result = new PointsAggregateResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SumEstimationAggregateResult") {
+ let result = new SumEstimationAggregateResult();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'AggregateResult' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ data["HasResult"] = this.hasResult;
+ data["N"] = this.n;
+ return data;
+ }
+}
+
+export interface IAggregateResult {
+ hasResult?: boolean | undefined;
+ n?: number | undefined;
+}
+
+export class MarginAggregateResult extends AggregateResult implements IMarginAggregateResult {
+ margin?: number | undefined;
+ absolutMargin?: number | undefined;
+ sumOfSquare?: number | undefined;
+ sampleStandardDeviation?: number | undefined;
+ mean?: number | undefined;
+ ex?: number | undefined;
+ ex2?: number | undefined;
+ variance?: number | undefined;
+
+ constructor(data?: IMarginAggregateResult) {
+ super(data);
+ this._discriminator = "MarginAggregateResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.margin = data["Margin"];
+ this.absolutMargin = data["AbsolutMargin"];
+ this.sumOfSquare = data["SumOfSquare"];
+ this.sampleStandardDeviation = data["SampleStandardDeviation"];
+ this.mean = data["Mean"];
+ this.ex = data["Ex"];
+ this.ex2 = data["Ex2"];
+ this.variance = data["Variance"];
+ }
+ }
+
+ static fromJS(data: any): MarginAggregateResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new MarginAggregateResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Margin"] = this.margin;
+ data["AbsolutMargin"] = this.absolutMargin;
+ data["SumOfSquare"] = this.sumOfSquare;
+ data["SampleStandardDeviation"] = this.sampleStandardDeviation;
+ data["Mean"] = this.mean;
+ data["Ex"] = this.ex;
+ data["Ex2"] = this.ex2;
+ data["Variance"] = this.variance;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IMarginAggregateResult extends IAggregateResult {
+ margin?: number | undefined;
+ absolutMargin?: number | undefined;
+ sumOfSquare?: number | undefined;
+ sampleStandardDeviation?: number | undefined;
+ mean?: number | undefined;
+ ex?: number | undefined;
+ ex2?: number | undefined;
+ variance?: number | undefined;
+}
+
+export class DoubleValueAggregateResult extends AggregateResult implements IDoubleValueAggregateResult {
+ result?: number | undefined;
+ temporaryResult?: number | undefined;
+
+ constructor(data?: IDoubleValueAggregateResult) {
+ super(data);
+ this._discriminator = "DoubleValueAggregateResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.result = data["Result"];
+ this.temporaryResult = data["TemporaryResult"];
+ }
+ }
+
+ static fromJS(data: any): DoubleValueAggregateResult {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "SumEstimationAggregateResult") {
+ let result = new SumEstimationAggregateResult();
+ result.init(data);
+ return result;
+ }
+ let result = new DoubleValueAggregateResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Result"] = this.result;
+ data["TemporaryResult"] = this.temporaryResult;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDoubleValueAggregateResult extends IAggregateResult {
+ result?: number | undefined;
+ temporaryResult?: number | undefined;
+}
+
+export class PointsAggregateResult extends AggregateResult implements IPointsAggregateResult {
+ points?: Point[] | undefined;
+
+ constructor(data?: IPointsAggregateResult) {
+ super(data);
+ this._discriminator = "PointsAggregateResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Points"] && data["Points"].constructor === Array) {
+ this.points = [];
+ for (let item of data["Points"])
+ this.points.push(Point.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): PointsAggregateResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new PointsAggregateResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.points && this.points.constructor === Array) {
+ data["Points"] = [];
+ for (let item of this.points)
+ data["Points"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPointsAggregateResult extends IAggregateResult {
+ points?: Point[] | undefined;
+}
+
+export class Point implements IPoint {
+ x?: number | undefined;
+ y?: number | undefined;
+
+ constructor(data?: IPoint) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.x = data["X"];
+ this.y = data["Y"];
+ }
+ }
+
+ static fromJS(data: any): Point {
+ data = typeof data === 'object' ? data : {};
+ let result = new Point();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["X"] = this.x;
+ data["Y"] = this.y;
+ return data;
+ }
+}
+
+export interface IPoint {
+ x?: number | undefined;
+ y?: number | undefined;
+}
+
+export class SumEstimationAggregateResult extends DoubleValueAggregateResult implements ISumEstimationAggregateResult {
+ sum?: number | undefined;
+ sumEstimation?: number | undefined;
+
+ constructor(data?: ISumEstimationAggregateResult) {
+ super(data);
+ this._discriminator = "SumEstimationAggregateResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.sum = data["Sum"];
+ this.sumEstimation = data["SumEstimation"];
+ }
+ }
+
+ static fromJS(data: any): SumEstimationAggregateResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new SumEstimationAggregateResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Sum"] = this.sum;
+ data["SumEstimation"] = this.sumEstimation;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISumEstimationAggregateResult extends IDoubleValueAggregateResult {
+ sum?: number | undefined;
+ sumEstimation?: number | undefined;
+}
+
+export class Brush implements IBrush {
+ brushIndex?: number | undefined;
+ brushEnum?: BrushEnum | undefined;
+
+ constructor(data?: IBrush) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.brushIndex = data["BrushIndex"];
+ this.brushEnum = data["BrushEnum"];
+ }
+ }
+
+ static fromJS(data: any): Brush {
+ data = typeof data === 'object' ? data : {};
+ let result = new Brush();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["BrushIndex"] = this.brushIndex;
+ data["BrushEnum"] = this.brushEnum;
+ return data;
+ }
+}
+
+export interface IBrush {
+ brushIndex?: number | undefined;
+ brushEnum?: BrushEnum | undefined;
+}
+
+export enum BrushEnum {
+ Overlap = 0,
+ Rest = 1,
+ All = 2,
+ UserSpecified = 3,
+}
+
+export abstract class BinRange implements IBinRange {
+ minValue?: number | undefined;
+ maxValue?: number | undefined;
+ targetBinNumber?: number | undefined;
+
+ protected _discriminator: string;
+
+ constructor(data?: IBinRange) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "BinRange";
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.minValue = data["MinValue"];
+ this.maxValue = data["MaxValue"];
+ this.targetBinNumber = data["TargetBinNumber"];
+ }
+ }
+
+ static fromJS(data: any): BinRange {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "NominalBinRange") {
+ let result = new NominalBinRange();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "QuantitativeBinRange") {
+ let result = new QuantitativeBinRange();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AggregateBinRange") {
+ let result = new AggregateBinRange();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AlphabeticBinRange") {
+ let result = new AlphabeticBinRange();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DateTimeBinRange") {
+ let result = new DateTimeBinRange();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'BinRange' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ data["MinValue"] = this.minValue;
+ data["MaxValue"] = this.maxValue;
+ data["TargetBinNumber"] = this.targetBinNumber;
+ return data;
+ }
+}
+
+export interface IBinRange {
+ minValue?: number | undefined;
+ maxValue?: number | undefined;
+ targetBinNumber?: number | undefined;
+}
+
+export class NominalBinRange extends BinRange implements INominalBinRange {
+ labelsValue?: { [key: string]: number; } | undefined;
+ valuesLabel?: { [key: string]: string; } | undefined;
+
+ constructor(data?: INominalBinRange) {
+ super(data);
+ this._discriminator = "NominalBinRange";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["LabelsValue"]) {
+ this.labelsValue = {};
+ for (let key in data["LabelsValue"]) {
+ if (data["LabelsValue"].hasOwnProperty(key))
+ this.labelsValue[key] = data["LabelsValue"][key];
+ }
+ }
+ if (data["ValuesLabel"]) {
+ this.valuesLabel = {};
+ for (let key in data["ValuesLabel"]) {
+ if (data["ValuesLabel"].hasOwnProperty(key))
+ this.valuesLabel[key] = data["ValuesLabel"][key];
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): NominalBinRange {
+ data = typeof data === 'object' ? data : {};
+ let result = new NominalBinRange();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.labelsValue) {
+ data["LabelsValue"] = {};
+ for (let key in this.labelsValue) {
+ if (this.labelsValue.hasOwnProperty(key))
+ data["LabelsValue"][key] = this.labelsValue[key];
+ }
+ }
+ if (this.valuesLabel) {
+ data["ValuesLabel"] = {};
+ for (let key in this.valuesLabel) {
+ if (this.valuesLabel.hasOwnProperty(key))
+ data["ValuesLabel"][key] = this.valuesLabel[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface INominalBinRange extends IBinRange {
+ labelsValue?: { [key: string]: number; } | undefined;
+ valuesLabel?: { [key: string]: string; } | undefined;
+}
+
+export class QuantitativeBinRange extends BinRange implements IQuantitativeBinRange {
+ isIntegerRange?: boolean | undefined;
+ step?: number | undefined;
+
+ constructor(data?: IQuantitativeBinRange) {
+ super(data);
+ this._discriminator = "QuantitativeBinRange";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.isIntegerRange = data["IsIntegerRange"];
+ this.step = data["Step"];
+ }
+ }
+
+ static fromJS(data: any): QuantitativeBinRange {
+ data = typeof data === 'object' ? data : {};
+ let result = new QuantitativeBinRange();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["IsIntegerRange"] = this.isIntegerRange;
+ data["Step"] = this.step;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IQuantitativeBinRange extends IBinRange {
+ isIntegerRange?: boolean | undefined;
+ step?: number | undefined;
+}
+
+export class AggregateBinRange extends BinRange implements IAggregateBinRange {
+
+ constructor(data?: IAggregateBinRange) {
+ super(data);
+ this._discriminator = "AggregateBinRange";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): AggregateBinRange {
+ data = typeof data === 'object' ? data : {};
+ let result = new AggregateBinRange();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAggregateBinRange extends IBinRange {
+}
+
+export class AlphabeticBinRange extends BinRange implements IAlphabeticBinRange {
+ prefix?: string | undefined;
+ labelsValue?: { [key: string]: number; } | undefined;
+ valuesLabel?: { [key: string]: string; } | undefined;
+
+ constructor(data?: IAlphabeticBinRange) {
+ super(data);
+ this._discriminator = "AlphabeticBinRange";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.prefix = data["Prefix"];
+ if (data["LabelsValue"]) {
+ this.labelsValue = {};
+ for (let key in data["LabelsValue"]) {
+ if (data["LabelsValue"].hasOwnProperty(key))
+ this.labelsValue[key] = data["LabelsValue"][key];
+ }
+ }
+ if (data["ValuesLabel"]) {
+ this.valuesLabel = {};
+ for (let key in data["ValuesLabel"]) {
+ if (data["ValuesLabel"].hasOwnProperty(key))
+ this.valuesLabel[key] = data["ValuesLabel"][key];
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): AlphabeticBinRange {
+ data = typeof data === 'object' ? data : {};
+ let result = new AlphabeticBinRange();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Prefix"] = this.prefix;
+ if (this.labelsValue) {
+ data["LabelsValue"] = {};
+ for (let key in this.labelsValue) {
+ if (this.labelsValue.hasOwnProperty(key))
+ data["LabelsValue"][key] = this.labelsValue[key];
+ }
+ }
+ if (this.valuesLabel) {
+ data["ValuesLabel"] = {};
+ for (let key in this.valuesLabel) {
+ if (this.valuesLabel.hasOwnProperty(key))
+ data["ValuesLabel"][key] = this.valuesLabel[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAlphabeticBinRange extends IBinRange {
+ prefix?: string | undefined;
+ labelsValue?: { [key: string]: number; } | undefined;
+ valuesLabel?: { [key: string]: string; } | undefined;
+}
+
+export class DateTimeBinRange extends BinRange implements IDateTimeBinRange {
+ step?: DateTimeStep | undefined;
+
+ constructor(data?: IDateTimeBinRange) {
+ super(data);
+ this._discriminator = "DateTimeBinRange";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.step = data["Step"] ? DateTimeStep.fromJS(data["Step"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): DateTimeBinRange {
+ data = typeof data === 'object' ? data : {};
+ let result = new DateTimeBinRange();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Step"] = this.step ? this.step.toJSON() : <any>undefined;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDateTimeBinRange extends IBinRange {
+ step?: DateTimeStep | undefined;
+}
+
+export class DateTimeStep implements IDateTimeStep {
+ dateTimeStepGranularity?: DateTimeStepGranularity | undefined;
+ dateTimeStepValue?: number | undefined;
+ dateTimeStepMaxValue?: number | undefined;
+
+ constructor(data?: IDateTimeStep) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.dateTimeStepGranularity = data["DateTimeStepGranularity"];
+ this.dateTimeStepValue = data["DateTimeStepValue"];
+ this.dateTimeStepMaxValue = data["DateTimeStepMaxValue"];
+ }
+ }
+
+ static fromJS(data: any): DateTimeStep {
+ data = typeof data === 'object' ? data : {};
+ let result = new DateTimeStep();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["DateTimeStepGranularity"] = this.dateTimeStepGranularity;
+ data["DateTimeStepValue"] = this.dateTimeStepValue;
+ data["DateTimeStepMaxValue"] = this.dateTimeStepMaxValue;
+ return data;
+ }
+}
+
+export interface IDateTimeStep {
+ dateTimeStepGranularity?: DateTimeStepGranularity | undefined;
+ dateTimeStepValue?: number | undefined;
+ dateTimeStepMaxValue?: number | undefined;
+}
+
+export enum DateTimeStepGranularity {
+ Second = 0,
+ Minute = 1,
+ Hour = 2,
+ Day = 3,
+ Month = 4,
+ Year = 5,
+}
+
+export class Bin implements IBin {
+ aggregateResults?: AggregateResult[] | undefined;
+ count?: number | undefined;
+ binIndex?: BinIndex | undefined;
+ spans?: Span[] | undefined;
+ xSize?: number | undefined;
+ ySize?: number | undefined;
+
+ constructor(data?: IBin) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["AggregateResults"] && data["AggregateResults"].constructor === Array) {
+ this.aggregateResults = [];
+ for (let item of data["AggregateResults"]) {
+ let fromJs = AggregateResult.fromJS(item);
+ if (fromJs)
+ this.aggregateResults.push(fromJs);
+ }
+ }
+ this.count = data["Count"];
+ this.binIndex = data["BinIndex"] ? BinIndex.fromJS(data["BinIndex"]) : <any>undefined;
+ if (data["Spans"] && data["Spans"].constructor === Array) {
+ this.spans = [];
+ for (let item of data["Spans"])
+ this.spans.push(Span.fromJS(item));
+ }
+ this.xSize = data["XSize"];
+ this.ySize = data["YSize"];
+ }
+ }
+
+ static fromJS(data: any): Bin {
+ data = typeof data === 'object' ? data : {};
+ let result = new Bin();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.aggregateResults && this.aggregateResults.constructor === Array) {
+ data["AggregateResults"] = [];
+ for (let item of this.aggregateResults)
+ data["AggregateResults"].push(item.toJSON());
+ }
+ data["Count"] = this.count;
+ data["BinIndex"] = this.binIndex ? this.binIndex.toJSON() : <any>undefined;
+ if (this.spans && this.spans.constructor === Array) {
+ data["Spans"] = [];
+ for (let item of this.spans)
+ data["Spans"].push(item.toJSON());
+ }
+ data["XSize"] = this.xSize;
+ data["YSize"] = this.ySize;
+ return data;
+ }
+}
+
+export interface IBin {
+ aggregateResults?: AggregateResult[] | undefined;
+ count?: number | undefined;
+ binIndex?: BinIndex | undefined;
+ spans?: Span[] | undefined;
+ xSize?: number | undefined;
+ ySize?: number | undefined;
+}
+
+export class BinIndex implements IBinIndex {
+ indices?: number[] | undefined;
+ flatIndex?: number | undefined;
+
+ constructor(data?: IBinIndex) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["Indices"] && data["Indices"].constructor === Array) {
+ this.indices = [];
+ for (let item of data["Indices"])
+ this.indices.push(item);
+ }
+ this.flatIndex = data["FlatIndex"];
+ }
+ }
+
+ static fromJS(data: any): BinIndex {
+ data = typeof data === 'object' ? data : {};
+ let result = new BinIndex();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.indices && this.indices.constructor === Array) {
+ data["Indices"] = [];
+ for (let item of this.indices)
+ data["Indices"].push(item);
+ }
+ data["FlatIndex"] = this.flatIndex;
+ return data;
+ }
+}
+
+export interface IBinIndex {
+ indices?: number[] | undefined;
+ flatIndex?: number | undefined;
+}
+
+export class Span implements ISpan {
+ min?: number | undefined;
+ max?: number | undefined;
+ index?: number | undefined;
+
+ constructor(data?: ISpan) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.min = data["Min"];
+ this.max = data["Max"];
+ this.index = data["Index"];
+ }
+ }
+
+ static fromJS(data: any): Span {
+ data = typeof data === 'object' ? data : {};
+ let result = new Span();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Min"] = this.min;
+ data["Max"] = this.max;
+ data["Index"] = this.index;
+ return data;
+ }
+}
+
+export interface ISpan {
+ min?: number | undefined;
+ max?: number | undefined;
+ index?: number | undefined;
+}
+
+export class ModelWealthResult extends Result implements IModelWealthResult {
+ wealth?: number | undefined;
+ startWealth?: number | undefined;
+
+ constructor(data?: IModelWealthResult) {
+ super(data);
+ this._discriminator = "ModelWealthResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.wealth = data["Wealth"];
+ this.startWealth = data["StartWealth"];
+ }
+ }
+
+ static fromJS(data: any): ModelWealthResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new ModelWealthResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Wealth"] = this.wealth;
+ data["StartWealth"] = this.startWealth;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IModelWealthResult extends IResult {
+ wealth?: number | undefined;
+ startWealth?: number | undefined;
+}
+
+export abstract class HypothesisTestResult extends Result implements IHypothesisTestResult {
+ pValue?: number | undefined;
+ statistic?: number | undefined;
+ support?: number | undefined;
+ sampleSizes?: number[] | undefined;
+ errorMessage?: string | undefined;
+
+ constructor(data?: IHypothesisTestResult) {
+ super(data);
+ this._discriminator = "HypothesisTestResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.pValue = data["PValue"];
+ this.statistic = data["Statistic"];
+ this.support = data["Support"];
+ if (data["SampleSizes"] && data["SampleSizes"].constructor === Array) {
+ this.sampleSizes = [];
+ for (let item of data["SampleSizes"])
+ this.sampleSizes.push(item);
+ }
+ this.errorMessage = data["ErrorMessage"];
+ }
+ }
+
+ static fromJS(data: any): HypothesisTestResult {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "ChiSquaredTestResult") {
+ let result = new ChiSquaredTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CorrelationTestResult") {
+ let result = new CorrelationTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "KSTestResult") {
+ let result = new KSTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "RootMeanSquareTestResult") {
+ let result = new RootMeanSquareTestResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "TTestResult") {
+ let result = new TTestResult();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'HypothesisTestResult' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["PValue"] = this.pValue;
+ data["Statistic"] = this.statistic;
+ data["Support"] = this.support;
+ if (this.sampleSizes && this.sampleSizes.constructor === Array) {
+ data["SampleSizes"] = [];
+ for (let item of this.sampleSizes)
+ data["SampleSizes"].push(item);
+ }
+ data["ErrorMessage"] = this.errorMessage;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IHypothesisTestResult extends IResult {
+ pValue?: number | undefined;
+ statistic?: number | undefined;
+ support?: number | undefined;
+ sampleSizes?: number[] | undefined;
+ errorMessage?: string | undefined;
+}
+
+export abstract class ModelOperationResult extends Result implements IModelOperationResult {
+ modelId?: ModelId | undefined;
+
+ constructor(data?: IModelOperationResult) {
+ super(data);
+ this._discriminator = "ModelOperationResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): ModelOperationResult {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "NewModelOperationResult") {
+ let result = new NewModelOperationResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "AddComparisonResult") {
+ let result = new AddComparisonResult();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "GetModelStateResult") {
+ let result = new GetModelStateResult();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'ModelOperationResult' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IModelOperationResult extends IResult {
+ modelId?: ModelId | undefined;
+}
+
+export class RecommenderResult extends Result implements IRecommenderResult {
+ recommendedHistograms?: RecommendedHistogram[] | undefined;
+ totalCount?: number | undefined;
+
+ constructor(data?: IRecommenderResult) {
+ super(data);
+ this._discriminator = "RecommenderResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["RecommendedHistograms"] && data["RecommendedHistograms"].constructor === Array) {
+ this.recommendedHistograms = [];
+ for (let item of data["RecommendedHistograms"])
+ this.recommendedHistograms.push(RecommendedHistogram.fromJS(item));
+ }
+ this.totalCount = data["TotalCount"];
+ }
+ }
+
+ static fromJS(data: any): RecommenderResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new RecommenderResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.recommendedHistograms && this.recommendedHistograms.constructor === Array) {
+ data["RecommendedHistograms"] = [];
+ for (let item of this.recommendedHistograms)
+ data["RecommendedHistograms"].push(item.toJSON());
+ }
+ data["TotalCount"] = this.totalCount;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IRecommenderResult extends IResult {
+ recommendedHistograms?: RecommendedHistogram[] | undefined;
+ totalCount?: number | undefined;
+}
+
+export class RecommendedHistogram implements IRecommendedHistogram {
+ histogramResult?: HistogramResult | undefined;
+ selectedBinIndices?: BinIndex[] | undefined;
+ selections?: Selection[] | undefined;
+ pValue?: number | undefined;
+ significance?: boolean | undefined;
+ decision?: Decision | undefined;
+ effectSize?: number | undefined;
+ hypothesisTestResult?: HypothesisTestResult | undefined;
+ id?: string | undefined;
+ xAttribute?: Attribute | undefined;
+ yAttribute?: Attribute | undefined;
+
+ constructor(data?: IRecommendedHistogram) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.histogramResult = data["HistogramResult"] ? HistogramResult.fromJS(data["HistogramResult"]) : <any>undefined;
+ if (data["SelectedBinIndices"] && data["SelectedBinIndices"].constructor === Array) {
+ this.selectedBinIndices = [];
+ for (let item of data["SelectedBinIndices"])
+ this.selectedBinIndices.push(BinIndex.fromJS(item));
+ }
+ if (data["Selections"] && data["Selections"].constructor === Array) {
+ this.selections = [];
+ for (let item of data["Selections"])
+ this.selections.push(Selection.fromJS(item));
+ }
+ this.pValue = data["PValue"];
+ this.significance = data["Significance"];
+ this.decision = data["Decision"] ? Decision.fromJS(data["Decision"]) : <any>undefined;
+ this.effectSize = data["EffectSize"];
+ this.hypothesisTestResult = data["HypothesisTestResult"] ? HypothesisTestResult.fromJS(data["HypothesisTestResult"]) : <any>undefined;
+ this.id = data["Id"];
+ this.xAttribute = data["XAttribute"] ? Attribute.fromJS(data["XAttribute"]) : <any>undefined;
+ this.yAttribute = data["YAttribute"] ? Attribute.fromJS(data["YAttribute"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): RecommendedHistogram {
+ data = typeof data === 'object' ? data : {};
+ let result = new RecommendedHistogram();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["HistogramResult"] = this.histogramResult ? this.histogramResult.toJSON() : <any>undefined;
+ if (this.selectedBinIndices && this.selectedBinIndices.constructor === Array) {
+ data["SelectedBinIndices"] = [];
+ for (let item of this.selectedBinIndices)
+ data["SelectedBinIndices"].push(item.toJSON());
+ }
+ if (this.selections && this.selections.constructor === Array) {
+ data["Selections"] = [];
+ for (let item of this.selections)
+ data["Selections"].push(item.toJSON());
+ }
+ data["PValue"] = this.pValue;
+ data["Significance"] = this.significance;
+ data["Decision"] = this.decision ? this.decision.toJSON() : <any>undefined;
+ data["EffectSize"] = this.effectSize;
+ data["HypothesisTestResult"] = this.hypothesisTestResult ? this.hypothesisTestResult.toJSON() : <any>undefined;
+ data["Id"] = this.id;
+ data["XAttribute"] = this.xAttribute ? this.xAttribute.toJSON() : <any>undefined;
+ data["YAttribute"] = this.yAttribute ? this.yAttribute.toJSON() : <any>undefined;
+ return data;
+ }
+}
+
+export interface IRecommendedHistogram {
+ histogramResult?: HistogramResult | undefined;
+ selectedBinIndices?: BinIndex[] | undefined;
+ selections?: Selection[] | undefined;
+ pValue?: number | undefined;
+ significance?: boolean | undefined;
+ decision?: Decision | undefined;
+ effectSize?: number | undefined;
+ hypothesisTestResult?: HypothesisTestResult | undefined;
+ id?: string | undefined;
+ xAttribute?: Attribute | undefined;
+ yAttribute?: Attribute | undefined;
+}
+
+export class Selection implements ISelection {
+ statements?: Statement[] | undefined;
+ filterHistogramOperationReference?: IOperationReference | undefined;
+
+ constructor(data?: ISelection) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["Statements"] && data["Statements"].constructor === Array) {
+ this.statements = [];
+ for (let item of data["Statements"])
+ this.statements.push(Statement.fromJS(item));
+ }
+ this.filterHistogramOperationReference = data["FilterHistogramOperationReference"] ? IOperationReference.fromJS(data["FilterHistogramOperationReference"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): Selection {
+ data = typeof data === 'object' ? data : {};
+ let result = new Selection();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.statements && this.statements.constructor === Array) {
+ data["Statements"] = [];
+ for (let item of this.statements)
+ data["Statements"].push(item.toJSON());
+ }
+ data["FilterHistogramOperationReference"] = this.filterHistogramOperationReference ? this.filterHistogramOperationReference.toJSON() : <any>undefined;
+ return data;
+ }
+}
+
+export interface ISelection {
+ statements?: Statement[] | undefined;
+ filterHistogramOperationReference?: IOperationReference | undefined;
+}
+
+export class Statement implements IStatement {
+ attribute?: Attribute | undefined;
+ predicate?: Predicate | undefined;
+ value?: any | undefined;
+
+ constructor(data?: IStatement) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.attribute = data["Attribute"] ? Attribute.fromJS(data["Attribute"]) : <any>undefined;
+ this.predicate = data["Predicate"];
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): Statement {
+ data = typeof data === 'object' ? data : {};
+ let result = new Statement();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Attribute"] = this.attribute ? this.attribute.toJSON() : <any>undefined;
+ data["Predicate"] = this.predicate;
+ data["Value"] = this.value;
+ return data;
+ }
+}
+
+export interface IStatement {
+ attribute?: Attribute | undefined;
+ predicate?: Predicate | undefined;
+ value?: any | undefined;
+}
+
+export enum Predicate {
+ EQUALS = 0,
+ LIKE = 1,
+ GREATER_THAN = 2,
+ LESS_THAN = 3,
+ GREATER_THAN_EQUAL = 4,
+ LESS_THAN_EQUAL = 5,
+ STARTS_WITH = 6,
+ ENDS_WITH = 7,
+ CONTAINS = 8,
+}
+
+export abstract class IOperationReference implements IIOperationReference {
+ id?: string | undefined;
+
+ protected _discriminator: string;
+
+ constructor(data?: IIOperationReference) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "IOperationReference";
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.id = data["Id"];
+ }
+ }
+
+ static fromJS(data: any): IOperationReference {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "OperationReference") {
+ let result = new OperationReference();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'IOperationReference' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ data["Id"] = this.id;
+ return data;
+ }
+}
+
+export interface IIOperationReference {
+ id?: string | undefined;
+}
+
+export class OperationReference extends IOperationReference implements IOperationReference {
+ id?: string | undefined;
+
+ constructor(data?: IOperationReference) {
+ super(data);
+ this._discriminator = "OperationReference";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.id = data["Id"];
+ }
+ }
+
+ static fromJS(data: any): OperationReference {
+ data = typeof data === 'object' ? data : {};
+ let result = new OperationReference();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Id"] = this.id;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IOperationReference extends IIOperationReference {
+ id?: string | undefined;
+}
+
+export class Decision extends Result implements IDecision {
+ comparisonId?: ComparisonId | undefined;
+ riskControlType?: RiskControlType | undefined;
+ significance?: boolean | undefined;
+ pValue?: number | undefined;
+ lhs?: number | undefined;
+ significanceLevel?: number | undefined;
+ sampleSizeEstimate?: number | undefined;
+
+ constructor(data?: IDecision) {
+ super(data);
+ this._discriminator = "Decision";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.comparisonId = data["ComparisonId"] ? ComparisonId.fromJS(data["ComparisonId"]) : <any>undefined;
+ this.riskControlType = data["RiskControlType"];
+ this.significance = data["Significance"];
+ this.pValue = data["PValue"];
+ this.lhs = data["Lhs"];
+ this.significanceLevel = data["SignificanceLevel"];
+ this.sampleSizeEstimate = data["SampleSizeEstimate"];
+ }
+ }
+
+ static fromJS(data: any): Decision {
+ data = typeof data === 'object' ? data : {};
+ let result = new Decision();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["ComparisonId"] = this.comparisonId ? this.comparisonId.toJSON() : <any>undefined;
+ data["RiskControlType"] = this.riskControlType;
+ data["Significance"] = this.significance;
+ data["PValue"] = this.pValue;
+ data["Lhs"] = this.lhs;
+ data["SignificanceLevel"] = this.significanceLevel;
+ data["SampleSizeEstimate"] = this.sampleSizeEstimate;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDecision extends IResult {
+ comparisonId?: ComparisonId | undefined;
+ riskControlType?: RiskControlType | undefined;
+ significance?: boolean | undefined;
+ pValue?: number | undefined;
+ lhs?: number | undefined;
+ significanceLevel?: number | undefined;
+ sampleSizeEstimate?: number | undefined;
+}
+
+export class ComparisonId implements IComparisonId {
+ value?: string | undefined;
+
+ constructor(data?: IComparisonId) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): ComparisonId {
+ data = typeof data === 'object' ? data : {};
+ let result = new ComparisonId();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ return data;
+ }
+}
+
+export interface IComparisonId {
+ value?: string | undefined;
+}
+
+export class OptimizerResult extends Result implements IOptimizerResult {
+ topKSolutions?: Solution[] | undefined;
+
+ constructor(data?: IOptimizerResult) {
+ super(data);
+ this._discriminator = "OptimizerResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["TopKSolutions"] && data["TopKSolutions"].constructor === Array) {
+ this.topKSolutions = [];
+ for (let item of data["TopKSolutions"])
+ this.topKSolutions.push(Solution.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): OptimizerResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new OptimizerResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.topKSolutions && this.topKSolutions.constructor === Array) {
+ data["TopKSolutions"] = [];
+ for (let item of this.topKSolutions)
+ data["TopKSolutions"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IOptimizerResult extends IResult {
+ topKSolutions?: Solution[] | undefined;
+}
+
+export class Solution implements ISolution {
+ solutionId?: string | undefined;
+ stepDescriptions?: StepDescription[] | undefined;
+ pipelineDescription?: PipelineDescription | undefined;
+ score?: Score | undefined;
+ naiveScore?: Score | undefined;
+
+ constructor(data?: ISolution) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.solutionId = data["SolutionId"];
+ if (data["StepDescriptions"] && data["StepDescriptions"].constructor === Array) {
+ this.stepDescriptions = [];
+ for (let item of data["StepDescriptions"])
+ this.stepDescriptions.push(StepDescription.fromJS(item));
+ }
+ this.pipelineDescription = data["PipelineDescription"] ? PipelineDescription.fromJS(data["PipelineDescription"]) : <any>undefined;
+ this.score = data["Score"] ? Score.fromJS(data["Score"]) : <any>undefined;
+ this.naiveScore = data["NaiveScore"] ? Score.fromJS(data["NaiveScore"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): Solution {
+ data = typeof data === 'object' ? data : {};
+ let result = new Solution();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["SolutionId"] = this.solutionId;
+ if (this.stepDescriptions && this.stepDescriptions.constructor === Array) {
+ data["StepDescriptions"] = [];
+ for (let item of this.stepDescriptions)
+ data["StepDescriptions"].push(item.toJSON());
+ }
+ data["PipelineDescription"] = this.pipelineDescription ? this.pipelineDescription.toJSON() : <any>undefined;
+ data["Score"] = this.score ? this.score.toJSON() : <any>undefined;
+ data["NaiveScore"] = this.naiveScore ? this.naiveScore.toJSON() : <any>undefined;
+ return data;
+ }
+}
+
+export interface ISolution {
+ solutionId?: string | undefined;
+ stepDescriptions?: StepDescription[] | undefined;
+ pipelineDescription?: PipelineDescription | undefined;
+ score?: Score | undefined;
+ naiveScore?: Score | undefined;
+}
+
+export class StepDescription implements IStepDescription {
+
+ protected _discriminator: string;
+
+ constructor(data?: IStepDescription) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "StepDescription";
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): StepDescription {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "SubpipelineStepDescription") {
+ let result = new SubpipelineStepDescription();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "PrimitiveStepDescription") {
+ let result = new PrimitiveStepDescription();
+ result.init(data);
+ return result;
+ }
+ let result = new StepDescription();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ return data;
+ }
+}
+
+export interface IStepDescription {
+}
+
+export class SubpipelineStepDescription extends StepDescription implements ISubpipelineStepDescription {
+ steps?: StepDescription[] | undefined;
+
+ constructor(data?: ISubpipelineStepDescription) {
+ super(data);
+ this._discriminator = "SubpipelineStepDescription";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Steps"] && data["Steps"].constructor === Array) {
+ this.steps = [];
+ for (let item of data["Steps"])
+ this.steps.push(StepDescription.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): SubpipelineStepDescription {
+ data = typeof data === 'object' ? data : {};
+ let result = new SubpipelineStepDescription();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.steps && this.steps.constructor === Array) {
+ data["Steps"] = [];
+ for (let item of this.steps)
+ data["Steps"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISubpipelineStepDescription extends IStepDescription {
+ steps?: StepDescription[] | undefined;
+}
+
+export class PipelineDescription implements IPipelineDescription {
+ id?: string | undefined;
+ name?: string | undefined;
+ description?: string | undefined;
+ inputs?: PipelineDescriptionInput[] | undefined;
+ outputs?: PipelineDescriptionOutput[] | undefined;
+ steps?: PipelineDescriptionStep[] | undefined;
+
+ constructor(data?: IPipelineDescription) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.id = data["Id"];
+ this.name = data["Name"];
+ this.description = data["Description"];
+ if (data["Inputs"] && data["Inputs"].constructor === Array) {
+ this.inputs = [];
+ for (let item of data["Inputs"])
+ this.inputs.push(PipelineDescriptionInput.fromJS(item));
+ }
+ if (data["Outputs"] && data["Outputs"].constructor === Array) {
+ this.outputs = [];
+ for (let item of data["Outputs"])
+ this.outputs.push(PipelineDescriptionOutput.fromJS(item));
+ }
+ if (data["Steps"] && data["Steps"].constructor === Array) {
+ this.steps = [];
+ for (let item of data["Steps"])
+ this.steps.push(PipelineDescriptionStep.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): PipelineDescription {
+ data = typeof data === 'object' ? data : {};
+ let result = new PipelineDescription();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Id"] = this.id;
+ data["Name"] = this.name;
+ data["Description"] = this.description;
+ if (this.inputs && this.inputs.constructor === Array) {
+ data["Inputs"] = [];
+ for (let item of this.inputs)
+ data["Inputs"].push(item.toJSON());
+ }
+ if (this.outputs && this.outputs.constructor === Array) {
+ data["Outputs"] = [];
+ for (let item of this.outputs)
+ data["Outputs"].push(item.toJSON());
+ }
+ if (this.steps && this.steps.constructor === Array) {
+ data["Steps"] = [];
+ for (let item of this.steps)
+ data["Steps"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface IPipelineDescription {
+ id?: string | undefined;
+ name?: string | undefined;
+ description?: string | undefined;
+ inputs?: PipelineDescriptionInput[] | undefined;
+ outputs?: PipelineDescriptionOutput[] | undefined;
+ steps?: PipelineDescriptionStep[] | undefined;
+}
+
+export class PipelineDescriptionInput implements IPipelineDescriptionInput {
+ name?: string | undefined;
+
+ constructor(data?: IPipelineDescriptionInput) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.name = data["Name"];
+ }
+ }
+
+ static fromJS(data: any): PipelineDescriptionInput {
+ data = typeof data === 'object' ? data : {};
+ let result = new PipelineDescriptionInput();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Name"] = this.name;
+ return data;
+ }
+}
+
+export interface IPipelineDescriptionInput {
+ name?: string | undefined;
+}
+
+export class PipelineDescriptionOutput implements IPipelineDescriptionOutput {
+ name?: string | undefined;
+ data?: string | undefined;
+
+ constructor(data?: IPipelineDescriptionOutput) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.name = data["Name"];
+ this.data = data["Data"];
+ }
+ }
+
+ static fromJS(data: any): PipelineDescriptionOutput {
+ data = typeof data === 'object' ? data : {};
+ let result = new PipelineDescriptionOutput();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Name"] = this.name;
+ data["Data"] = this.data;
+ return data;
+ }
+}
+
+export interface IPipelineDescriptionOutput {
+ name?: string | undefined;
+ data?: string | undefined;
+}
+
+export class PipelineDescriptionStep implements IPipelineDescriptionStep {
+ outputs?: StepOutput[] | undefined;
+
+ protected _discriminator: string;
+
+ constructor(data?: IPipelineDescriptionStep) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "PipelineDescriptionStep";
+ }
+
+ init(data?: any) {
+ if (data) {
+ if (data["Outputs"] && data["Outputs"].constructor === Array) {
+ this.outputs = [];
+ for (let item of data["Outputs"])
+ this.outputs.push(StepOutput.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): PipelineDescriptionStep {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "PlaceholderPipelineDescriptionStep") {
+ let result = new PlaceholderPipelineDescriptionStep();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "SubpipelinePipelineDescriptionStep") {
+ let result = new SubpipelinePipelineDescriptionStep();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "PrimitivePipelineDescriptionStep") {
+ let result = new PrimitivePipelineDescriptionStep();
+ result.init(data);
+ return result;
+ }
+ let result = new PipelineDescriptionStep();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ if (this.outputs && this.outputs.constructor === Array) {
+ data["Outputs"] = [];
+ for (let item of this.outputs)
+ data["Outputs"].push(item.toJSON());
+ }
+ return data;
+ }
+}
+
+export interface IPipelineDescriptionStep {
+ outputs?: StepOutput[] | undefined;
+}
+
+export class StepOutput implements IStepOutput {
+ id?: string | undefined;
+
+ constructor(data?: IStepOutput) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.id = data["Id"];
+ }
+ }
+
+ static fromJS(data: any): StepOutput {
+ data = typeof data === 'object' ? data : {};
+ let result = new StepOutput();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Id"] = this.id;
+ return data;
+ }
+}
+
+export interface IStepOutput {
+ id?: string | undefined;
+}
+
+export class PlaceholderPipelineDescriptionStep extends PipelineDescriptionStep implements IPlaceholderPipelineDescriptionStep {
+ inputs?: StepInput[] | undefined;
+
+ constructor(data?: IPlaceholderPipelineDescriptionStep) {
+ super(data);
+ this._discriminator = "PlaceholderPipelineDescriptionStep";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Inputs"] && data["Inputs"].constructor === Array) {
+ this.inputs = [];
+ for (let item of data["Inputs"])
+ this.inputs.push(StepInput.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): PlaceholderPipelineDescriptionStep {
+ data = typeof data === 'object' ? data : {};
+ let result = new PlaceholderPipelineDescriptionStep();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.inputs && this.inputs.constructor === Array) {
+ data["Inputs"] = [];
+ for (let item of this.inputs)
+ data["Inputs"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPlaceholderPipelineDescriptionStep extends IPipelineDescriptionStep {
+ inputs?: StepInput[] | undefined;
+}
+
+export class StepInput implements IStepInput {
+ data?: string | undefined;
+
+ constructor(data?: IStepInput) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.data = data["Data"];
+ }
+ }
+
+ static fromJS(data: any): StepInput {
+ data = typeof data === 'object' ? data : {};
+ let result = new StepInput();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Data"] = this.data;
+ return data;
+ }
+}
+
+export interface IStepInput {
+ data?: string | undefined;
+}
+
+export class SubpipelinePipelineDescriptionStep extends PipelineDescriptionStep implements ISubpipelinePipelineDescriptionStep {
+ pipelineDescription?: PipelineDescription | undefined;
+ inputs?: StepInput[] | undefined;
+
+ constructor(data?: ISubpipelinePipelineDescriptionStep) {
+ super(data);
+ this._discriminator = "SubpipelinePipelineDescriptionStep";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.pipelineDescription = data["PipelineDescription"] ? PipelineDescription.fromJS(data["PipelineDescription"]) : <any>undefined;
+ if (data["Inputs"] && data["Inputs"].constructor === Array) {
+ this.inputs = [];
+ for (let item of data["Inputs"])
+ this.inputs.push(StepInput.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): SubpipelinePipelineDescriptionStep {
+ data = typeof data === 'object' ? data : {};
+ let result = new SubpipelinePipelineDescriptionStep();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["PipelineDescription"] = this.pipelineDescription ? this.pipelineDescription.toJSON() : <any>undefined;
+ if (this.inputs && this.inputs.constructor === Array) {
+ data["Inputs"] = [];
+ for (let item of this.inputs)
+ data["Inputs"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISubpipelinePipelineDescriptionStep extends IPipelineDescriptionStep {
+ pipelineDescription?: PipelineDescription | undefined;
+ inputs?: StepInput[] | undefined;
+}
+
+export class PrimitivePipelineDescriptionStep extends PipelineDescriptionStep implements IPrimitivePipelineDescriptionStep {
+ primitive?: Primitive | undefined;
+ arguments?: { [key: string]: PrimitiveStepArgument; } | undefined;
+ hyperparams?: { [key: string]: PrimitiveStepHyperparameter; } | undefined;
+
+ constructor(data?: IPrimitivePipelineDescriptionStep) {
+ super(data);
+ this._discriminator = "PrimitivePipelineDescriptionStep";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.primitive = data["Primitive"] ? Primitive.fromJS(data["Primitive"]) : <any>undefined;
+ if (data["Arguments"]) {
+ this.arguments = {};
+ for (let key in data["Arguments"]) {
+ if (data["Arguments"].hasOwnProperty(key))
+ this.arguments[key] = data["Arguments"][key] ? PrimitiveStepArgument.fromJS(data["Arguments"][key]) : new PrimitiveStepArgument();
+ }
+ }
+ if (data["Hyperparams"]) {
+ this.hyperparams = {};
+ for (let key in data["Hyperparams"]) {
+ if (data["Hyperparams"].hasOwnProperty(key))
+ this.hyperparams[key] = data["Hyperparams"][key] ? PrimitiveStepHyperparameter.fromJS(data["Hyperparams"][key]) : new PrimitiveStepHyperparameter();
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): PrimitivePipelineDescriptionStep {
+ data = typeof data === 'object' ? data : {};
+ let result = new PrimitivePipelineDescriptionStep();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Primitive"] = this.primitive ? this.primitive.toJSON() : <any>undefined;
+ if (this.arguments) {
+ data["Arguments"] = {};
+ for (let key in this.arguments) {
+ if (this.arguments.hasOwnProperty(key))
+ data["Arguments"][key] = this.arguments[key];
+ }
+ }
+ if (this.hyperparams) {
+ data["Hyperparams"] = {};
+ for (let key in this.hyperparams) {
+ if (this.hyperparams.hasOwnProperty(key))
+ data["Hyperparams"][key] = this.hyperparams[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPrimitivePipelineDescriptionStep extends IPipelineDescriptionStep {
+ primitive?: Primitive | undefined;
+ arguments?: { [key: string]: PrimitiveStepArgument; } | undefined;
+ hyperparams?: { [key: string]: PrimitiveStepHyperparameter; } | undefined;
+}
+
+export class Primitive implements IPrimitive {
+ id?: string | undefined;
+ version?: string | undefined;
+ pythonPath?: string | undefined;
+ name?: string | undefined;
+ digest?: string | undefined;
+
+ constructor(data?: IPrimitive) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.id = data["Id"];
+ this.version = data["Version"];
+ this.pythonPath = data["PythonPath"];
+ this.name = data["Name"];
+ this.digest = data["Digest"];
+ }
+ }
+
+ static fromJS(data: any): Primitive {
+ data = typeof data === 'object' ? data : {};
+ let result = new Primitive();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Id"] = this.id;
+ data["Version"] = this.version;
+ data["PythonPath"] = this.pythonPath;
+ data["Name"] = this.name;
+ data["Digest"] = this.digest;
+ return data;
+ }
+}
+
+export interface IPrimitive {
+ id?: string | undefined;
+ version?: string | undefined;
+ pythonPath?: string | undefined;
+ name?: string | undefined;
+ digest?: string | undefined;
+}
+
+export class PrimitiveStepHyperparameter implements IPrimitiveStepHyperparameter {
+
+ protected _discriminator: string;
+
+ constructor(data?: IPrimitiveStepHyperparameter) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "PrimitiveStepHyperparameter";
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): PrimitiveStepHyperparameter {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "PrimitiveStepArgument") {
+ let result = new PrimitiveStepArgument();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DataArguments") {
+ let result = new DataArguments();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "PrimitiveArgument") {
+ let result = new PrimitiveArgument();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "PrimitiveArguments") {
+ let result = new PrimitiveArguments();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "ValueArgument") {
+ let result = new ValueArgument();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "ContainerArgument") {
+ let result = new ContainerArgument();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DataArgument") {
+ let result = new DataArgument();
+ result.init(data);
+ return result;
+ }
+ let result = new PrimitiveStepHyperparameter();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ return data;
+ }
+}
+
+export interface IPrimitiveStepHyperparameter {
+}
+
+export class PrimitiveStepArgument extends PrimitiveStepHyperparameter implements IPrimitiveStepArgument {
+
+ protected _discriminator: string;
+
+ constructor(data?: IPrimitiveStepArgument) {
+ super(data);
+ this._discriminator = "PrimitiveStepArgument";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): PrimitiveStepArgument {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "ContainerArgument") {
+ let result = new ContainerArgument();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DataArgument") {
+ let result = new DataArgument();
+ result.init(data);
+ return result;
+ }
+ let result = new PrimitiveStepArgument();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPrimitiveStepArgument extends IPrimitiveStepHyperparameter {
+}
+
+export class DataArguments extends PrimitiveStepHyperparameter implements IDataArguments {
+ data?: string[] | undefined;
+
+ constructor(data?: IDataArguments) {
+ super(data);
+ this._discriminator = "DataArguments";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Data"] && data["Data"].constructor === Array) {
+ this.data = [];
+ for (let item of data["Data"])
+ this.data.push(item);
+ }
+ }
+ }
+
+ static fromJS(data: any): DataArguments {
+ data = typeof data === 'object' ? data : {};
+ let result = new DataArguments();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.data && this.data.constructor === Array) {
+ data["Data"] = [];
+ for (let item of this.data)
+ data["Data"].push(item);
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDataArguments extends IPrimitiveStepHyperparameter {
+ data?: string[] | undefined;
+}
+
+export class PrimitiveArgument extends PrimitiveStepHyperparameter implements IPrimitiveArgument {
+ data?: number | undefined;
+
+ constructor(data?: IPrimitiveArgument) {
+ super(data);
+ this._discriminator = "PrimitiveArgument";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.data = data["Data"];
+ }
+ }
+
+ static fromJS(data: any): PrimitiveArgument {
+ data = typeof data === 'object' ? data : {};
+ let result = new PrimitiveArgument();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Data"] = this.data;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPrimitiveArgument extends IPrimitiveStepHyperparameter {
+ data?: number | undefined;
+}
+
+export class PrimitiveArguments extends PrimitiveStepHyperparameter implements IPrimitiveArguments {
+ data?: number[] | undefined;
+
+ constructor(data?: IPrimitiveArguments) {
+ super(data);
+ this._discriminator = "PrimitiveArguments";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Data"] && data["Data"].constructor === Array) {
+ this.data = [];
+ for (let item of data["Data"])
+ this.data.push(item);
+ }
+ }
+ }
+
+ static fromJS(data: any): PrimitiveArguments {
+ data = typeof data === 'object' ? data : {};
+ let result = new PrimitiveArguments();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.data && this.data.constructor === Array) {
+ data["Data"] = [];
+ for (let item of this.data)
+ data["Data"].push(item);
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPrimitiveArguments extends IPrimitiveStepHyperparameter {
+ data?: number[] | undefined;
+}
+
+export class ValueArgument extends PrimitiveStepHyperparameter implements IValueArgument {
+ data?: Value | undefined;
+
+ constructor(data?: IValueArgument) {
+ super(data);
+ this._discriminator = "ValueArgument";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.data = data["Data"] ? Value.fromJS(data["Data"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): ValueArgument {
+ data = typeof data === 'object' ? data : {};
+ let result = new ValueArgument();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Data"] = this.data ? this.data.toJSON() : <any>undefined;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IValueArgument extends IPrimitiveStepHyperparameter {
+ data?: Value | undefined;
+}
+
+export abstract class Value implements IValue {
+
+ protected _discriminator: string;
+
+ constructor(data?: IValue) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ this._discriminator = "Value";
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): Value {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "ErrorValue") {
+ let result = new ErrorValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DoubleValue") {
+ let result = new DoubleValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "LongValue") {
+ let result = new LongValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "BoolValue") {
+ let result = new BoolValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "StringValue") {
+ let result = new StringValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "DatasetUriValue") {
+ let result = new DatasetUriValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "CsvUriValue") {
+ let result = new CsvUriValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "PickleUriValue") {
+ let result = new PickleUriValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "PickleBlobValue") {
+ let result = new PickleBlobValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "PlasmaIdValue") {
+ let result = new PlasmaIdValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "BytesValue") {
+ let result = new BytesValue();
+ result.init(data);
+ return result;
+ }
+ if (data["discriminator"] === "ListValue") {
+ let result = new ListValue();
+ result.init(data);
+ return result;
+ }
+ throw new Error("The abstract class 'Value' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ return data;
+ }
+}
+
+export interface IValue {
+}
+
+export class ErrorValue extends Value implements IErrorValue {
+ message?: string | undefined;
+
+ constructor(data?: IErrorValue) {
+ super(data);
+ this._discriminator = "ErrorValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.message = data["Message"];
+ }
+ }
+
+ static fromJS(data: any): ErrorValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new ErrorValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Message"] = this.message;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IErrorValue extends IValue {
+ message?: string | undefined;
+}
+
+export class DoubleValue extends Value implements IDoubleValue {
+ value?: number | undefined;
+
+ constructor(data?: IDoubleValue) {
+ super(data);
+ this._discriminator = "DoubleValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): DoubleValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new DoubleValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDoubleValue extends IValue {
+ value?: number | undefined;
+}
+
+export class LongValue extends Value implements ILongValue {
+ value?: number | undefined;
+
+ constructor(data?: ILongValue) {
+ super(data);
+ this._discriminator = "LongValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): LongValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new LongValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ILongValue extends IValue {
+ value?: number | undefined;
+}
+
+export class BoolValue extends Value implements IBoolValue {
+ value?: boolean | undefined;
+
+ constructor(data?: IBoolValue) {
+ super(data);
+ this._discriminator = "BoolValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): BoolValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new BoolValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IBoolValue extends IValue {
+ value?: boolean | undefined;
+}
+
+export class StringValue extends Value implements IStringValue {
+ value?: string | undefined;
+
+ constructor(data?: IStringValue) {
+ super(data);
+ this._discriminator = "StringValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): StringValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new StringValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IStringValue extends IValue {
+ value?: string | undefined;
+}
+
+export class DatasetUriValue extends Value implements IDatasetUriValue {
+ value?: string | undefined;
+
+ constructor(data?: IDatasetUriValue) {
+ super(data);
+ this._discriminator = "DatasetUriValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): DatasetUriValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new DatasetUriValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDatasetUriValue extends IValue {
+ value?: string | undefined;
+}
+
+export class CsvUriValue extends Value implements ICsvUriValue {
+ value?: string | undefined;
+
+ constructor(data?: ICsvUriValue) {
+ super(data);
+ this._discriminator = "CsvUriValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): CsvUriValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new CsvUriValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ICsvUriValue extends IValue {
+ value?: string | undefined;
+}
+
+export class PickleUriValue extends Value implements IPickleUriValue {
+ value?: string | undefined;
+
+ constructor(data?: IPickleUriValue) {
+ super(data);
+ this._discriminator = "PickleUriValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): PickleUriValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new PickleUriValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPickleUriValue extends IValue {
+ value?: string | undefined;
+}
+
+export class PickleBlobValue extends Value implements IPickleBlobValue {
+ value?: string | undefined;
+
+ constructor(data?: IPickleBlobValue) {
+ super(data);
+ this._discriminator = "PickleBlobValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): PickleBlobValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new PickleBlobValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPickleBlobValue extends IValue {
+ value?: string | undefined;
+}
+
+export class PlasmaIdValue extends Value implements IPlasmaIdValue {
+ value?: string | undefined;
+
+ constructor(data?: IPlasmaIdValue) {
+ super(data);
+ this._discriminator = "PlasmaIdValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): PlasmaIdValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new PlasmaIdValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPlasmaIdValue extends IValue {
+ value?: string | undefined;
+}
+
+export class BytesValue extends Value implements IBytesValue {
+ value?: string | undefined;
+
+ constructor(data?: IBytesValue) {
+ super(data);
+ this._discriminator = "BytesValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): BytesValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new BytesValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IBytesValue extends IValue {
+ value?: string | undefined;
+}
+
+export class ContainerArgument extends PrimitiveStepArgument implements IContainerArgument {
+ data?: string | undefined;
+
+ constructor(data?: IContainerArgument) {
+ super(data);
+ this._discriminator = "ContainerArgument";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.data = data["Data"];
+ }
+ }
+
+ static fromJS(data: any): ContainerArgument {
+ data = typeof data === 'object' ? data : {};
+ let result = new ContainerArgument();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Data"] = this.data;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IContainerArgument extends IPrimitiveStepArgument {
+ data?: string | undefined;
+}
+
+export class Score implements IScore {
+ metricType?: MetricType | undefined;
+ value?: number | undefined;
+
+ constructor(data?: IScore) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.metricType = data["MetricType"];
+ this.value = data["Value"];
+ }
+ }
+
+ static fromJS(data: any): Score {
+ data = typeof data === 'object' ? data : {};
+ let result = new Score();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["MetricType"] = this.metricType;
+ data["Value"] = this.value;
+ return data;
+ }
+}
+
+export interface IScore {
+ metricType?: MetricType | undefined;
+ value?: number | undefined;
+}
+
+export class ExampleResult extends Result implements IExampleResult {
+ resultValues?: { [key: string]: string; } | undefined;
+ message?: string | undefined;
+
+ constructor(data?: IExampleResult) {
+ super(data);
+ this._discriminator = "ExampleResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["ResultValues"]) {
+ this.resultValues = {};
+ for (let key in data["ResultValues"]) {
+ if (data["ResultValues"].hasOwnProperty(key))
+ this.resultValues[key] = data["ResultValues"][key];
+ }
+ }
+ this.message = data["Message"];
+ }
+ }
+
+ static fromJS(data: any): ExampleResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new ExampleResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.resultValues) {
+ data["ResultValues"] = {};
+ for (let key in this.resultValues) {
+ if (this.resultValues.hasOwnProperty(key))
+ data["ResultValues"][key] = this.resultValues[key];
+ }
+ }
+ data["Message"] = this.message;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IExampleResult extends IResult {
+ resultValues?: { [key: string]: string; } | undefined;
+ message?: string | undefined;
+}
+
+export class NewModelOperationResult extends ModelOperationResult implements INewModelOperationResult {
+
+ constructor(data?: INewModelOperationResult) {
+ super(data);
+ this._discriminator = "NewModelOperationResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): NewModelOperationResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new NewModelOperationResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface INewModelOperationResult extends IModelOperationResult {
+}
+
+export class AddComparisonResult extends ModelOperationResult implements IAddComparisonResult {
+ comparisonId?: ComparisonId | undefined;
+
+ constructor(data?: IAddComparisonResult) {
+ super(data);
+ this._discriminator = "AddComparisonResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.comparisonId = data["ComparisonId"] ? ComparisonId.fromJS(data["ComparisonId"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): AddComparisonResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new AddComparisonResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["ComparisonId"] = this.comparisonId ? this.comparisonId.toJSON() : <any>undefined;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAddComparisonResult extends IModelOperationResult {
+ comparisonId?: ComparisonId | undefined;
+}
+
+export class GetModelStateResult extends ModelOperationResult implements IGetModelStateResult {
+ decisions?: Decision[] | undefined;
+ startingWealth?: number | undefined;
+ currentWealth?: number | undefined;
+
+ constructor(data?: IGetModelStateResult) {
+ super(data);
+ this._discriminator = "GetModelStateResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Decisions"] && data["Decisions"].constructor === Array) {
+ this.decisions = [];
+ for (let item of data["Decisions"])
+ this.decisions.push(Decision.fromJS(item));
+ }
+ this.startingWealth = data["StartingWealth"];
+ this.currentWealth = data["CurrentWealth"];
+ }
+ }
+
+ static fromJS(data: any): GetModelStateResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new GetModelStateResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.decisions && this.decisions.constructor === Array) {
+ data["Decisions"] = [];
+ for (let item of this.decisions)
+ data["Decisions"].push(item.toJSON());
+ }
+ data["StartingWealth"] = this.startingWealth;
+ data["CurrentWealth"] = this.currentWealth;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IGetModelStateResult extends IModelOperationResult {
+ decisions?: Decision[] | undefined;
+ startingWealth?: number | undefined;
+ currentWealth?: number | undefined;
+}
+
+export class AggregateKey implements IAggregateKey {
+ aggregateParameterIndex?: number | undefined;
+ brushIndex?: number | undefined;
+
+ constructor(data?: IAggregateKey) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.aggregateParameterIndex = data["AggregateParameterIndex"];
+ this.brushIndex = data["BrushIndex"];
+ }
+ }
+
+ static fromJS(data: any): AggregateKey {
+ data = typeof data === 'object' ? data : {};
+ let result = new AggregateKey();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["AggregateParameterIndex"] = this.aggregateParameterIndex;
+ data["BrushIndex"] = this.brushIndex;
+ return data;
+ }
+}
+
+export interface IAggregateKey {
+ aggregateParameterIndex?: number | undefined;
+ brushIndex?: number | undefined;
+}
+
+export abstract class IResult implements IIResult {
+
+ constructor(data?: IIResult) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): IResult {
+ data = typeof data === 'object' ? data : {};
+ throw new Error("The abstract class 'IResult' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ return data;
+ }
+}
+
+export interface IIResult {
+}
+
+export class DataArgument extends PrimitiveStepArgument implements IDataArgument {
+ data?: string | undefined;
+
+ constructor(data?: IDataArgument) {
+ super(data);
+ this._discriminator = "DataArgument";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.data = data["Data"];
+ }
+ }
+
+ static fromJS(data: any): DataArgument {
+ data = typeof data === 'object' ? data : {};
+ let result = new DataArgument();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Data"] = this.data;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IDataArgument extends IPrimitiveStepArgument {
+ data?: string | undefined;
+}
+
+export class ListValue extends Value implements IListValue {
+ items?: Value[] | undefined;
+
+ constructor(data?: IListValue) {
+ super(data);
+ this._discriminator = "ListValue";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Items"] && data["Items"].constructor === Array) {
+ this.items = [];
+ for (let item of data["Items"])
+ this.items.push(Value.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): ListValue {
+ data = typeof data === 'object' ? data : {};
+ let result = new ListValue();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.items && this.items.constructor === Array) {
+ data["Items"] = [];
+ for (let item of this.items)
+ data["Items"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IListValue extends IValue {
+ items?: Value[] | undefined;
+}
+
+export class Metrics implements IMetrics {
+ averageAccuracy?: number | undefined;
+ averageRSquared?: number | undefined;
+ f1Macro?: number | undefined;
+
+ constructor(data?: IMetrics) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.averageAccuracy = data["AverageAccuracy"];
+ this.averageRSquared = data["AverageRSquared"];
+ this.f1Macro = data["F1Macro"];
+ }
+ }
+
+ static fromJS(data: any): Metrics {
+ data = typeof data === 'object' ? data : {};
+ let result = new Metrics();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["AverageAccuracy"] = this.averageAccuracy;
+ data["AverageRSquared"] = this.averageRSquared;
+ data["F1Macro"] = this.f1Macro;
+ return data;
+ }
+}
+
+export interface IMetrics {
+ averageAccuracy?: number | undefined;
+ averageRSquared?: number | undefined;
+ f1Macro?: number | undefined;
+}
+
+export class FeatureImportanceOperationParameters extends DistOperationParameters implements IFeatureImportanceOperationParameters {
+ solutionId?: string | undefined;
+
+ constructor(data?: IFeatureImportanceOperationParameters) {
+ super(data);
+ this._discriminator = "FeatureImportanceOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.solutionId = data["SolutionId"];
+ }
+ }
+
+ static fromJS(data: any): FeatureImportanceOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new FeatureImportanceOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["SolutionId"] = this.solutionId;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IFeatureImportanceOperationParameters extends IDistOperationParameters {
+ solutionId?: string | undefined;
+}
+
+export class FeatureImportanceResult extends Result implements IFeatureImportanceResult {
+ featureImportances?: { [key: string]: TupleOfDoubleAndDouble; } | undefined;
+
+ constructor(data?: IFeatureImportanceResult) {
+ super(data);
+ this._discriminator = "FeatureImportanceResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["FeatureImportances"]) {
+ this.featureImportances = {};
+ for (let key in data["FeatureImportances"]) {
+ if (data["FeatureImportances"].hasOwnProperty(key))
+ this.featureImportances[key] = data["FeatureImportances"][key] ? TupleOfDoubleAndDouble.fromJS(data["FeatureImportances"][key]) : new TupleOfDoubleAndDouble();
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): FeatureImportanceResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new FeatureImportanceResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.featureImportances) {
+ data["FeatureImportances"] = {};
+ for (let key in this.featureImportances) {
+ if (this.featureImportances.hasOwnProperty(key))
+ data["FeatureImportances"][key] = this.featureImportances[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IFeatureImportanceResult extends IResult {
+ featureImportances?: { [key: string]: TupleOfDoubleAndDouble; } | undefined;
+}
+
+export class TupleOfDoubleAndDouble implements ITupleOfDoubleAndDouble {
+ item1?: number | undefined;
+ item2?: number | undefined;
+
+ constructor(data?: ITupleOfDoubleAndDouble) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.item1 = data["Item1"];
+ this.item2 = data["Item2"];
+ }
+ }
+
+ static fromJS(data: any): TupleOfDoubleAndDouble {
+ data = typeof data === 'object' ? data : {};
+ let result = new TupleOfDoubleAndDouble();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Item1"] = this.item1;
+ data["Item2"] = this.item2;
+ return data;
+ }
+}
+
+export interface ITupleOfDoubleAndDouble {
+ item1?: number | undefined;
+ item2?: number | undefined;
+}
+
+export class PrimitiveStepDescription extends StepDescription implements IPrimitiveStepDescription {
+ hyperparams?: { [key: string]: Value; } | undefined;
+
+ constructor(data?: IPrimitiveStepDescription) {
+ super(data);
+ this._discriminator = "PrimitiveStepDescription";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Hyperparams"]) {
+ this.hyperparams = {};
+ for (let key in data["Hyperparams"]) {
+ if (data["Hyperparams"].hasOwnProperty(key))
+ this.hyperparams[key] = data["Hyperparams"][key] ? Value.fromJS(data["Hyperparams"][key]) : <any>undefined;
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): PrimitiveStepDescription {
+ data = typeof data === 'object' ? data : {};
+ let result = new PrimitiveStepDescription();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.hyperparams) {
+ data["Hyperparams"] = {};
+ for (let key in this.hyperparams) {
+ if (this.hyperparams.hasOwnProperty(key))
+ data["Hyperparams"][key] = this.hyperparams[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IPrimitiveStepDescription extends IStepDescription {
+ hyperparams?: { [key: string]: Value; } | undefined;
+}
+
+export enum ValueType {
+ VALUE_TYPE_UNDEFINED = 0,
+ RAW = 1,
+ DATASET_URI = 2,
+ CSV_URI = 3,
+ PICKLE_URI = 4,
+ PICKLE_BLOB = 5,
+ PLASMA_ID = 6,
+}
+
+export class DatamartSearchParameters implements IDatamartSearchParameters {
+ adapterName?: string | undefined;
+ queryJson?: string | undefined;
+
+ constructor(data?: IDatamartSearchParameters) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.adapterName = data["AdapterName"];
+ this.queryJson = data["QueryJson"];
+ }
+ }
+
+ static fromJS(data: any): DatamartSearchParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new DatamartSearchParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["AdapterName"] = this.adapterName;
+ data["QueryJson"] = this.queryJson;
+ return data;
+ }
+}
+
+export interface IDatamartSearchParameters {
+ adapterName?: string | undefined;
+ queryJson?: string | undefined;
+}
+
+export class DatamartAugmentParameters implements IDatamartAugmentParameters {
+ adapterName?: string | undefined;
+ augmentationJson?: string | undefined;
+ numberOfSamples?: number | undefined;
+ augmentedAdapterName?: string | undefined;
+
+ constructor(data?: IDatamartAugmentParameters) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.adapterName = data["AdapterName"];
+ this.augmentationJson = data["AugmentationJson"];
+ this.numberOfSamples = data["NumberOfSamples"];
+ this.augmentedAdapterName = data["AugmentedAdapterName"];
+ }
+ }
+
+ static fromJS(data: any): DatamartAugmentParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new DatamartAugmentParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["AdapterName"] = this.adapterName;
+ data["AugmentationJson"] = this.augmentationJson;
+ data["NumberOfSamples"] = this.numberOfSamples;
+ data["AugmentedAdapterName"] = this.augmentedAdapterName;
+ return data;
+ }
+}
+
+export interface IDatamartAugmentParameters {
+ adapterName?: string | undefined;
+ augmentationJson?: string | undefined;
+ numberOfSamples?: number | undefined;
+ augmentedAdapterName?: string | undefined;
+}
+
+export class RawDataResult extends DistResult implements IRawDataResult {
+ samples?: { [key: string]: any[]; } | undefined;
+ weightedWords?: { [key: string]: Word[]; } | undefined;
+
+ constructor(data?: IRawDataResult) {
+ super(data);
+ this._discriminator = "RawDataResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Samples"]) {
+ this.samples = {};
+ for (let key in data["Samples"]) {
+ if (data["Samples"].hasOwnProperty(key))
+ this.samples[key] = data["Samples"][key] !== undefined ? data["Samples"][key] : [];
+ }
+ }
+ if (data["WeightedWords"]) {
+ this.weightedWords = {};
+ for (let key in data["WeightedWords"]) {
+ if (data["WeightedWords"].hasOwnProperty(key))
+ this.weightedWords[key] = data["WeightedWords"][key] ? data["WeightedWords"][key].map((i: any) => Word.fromJS(i)) : [];
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): RawDataResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new RawDataResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.samples) {
+ data["Samples"] = {};
+ for (let key in this.samples) {
+ if (this.samples.hasOwnProperty(key))
+ data["Samples"][key] = this.samples[key];
+ }
+ }
+ if (this.weightedWords) {
+ data["WeightedWords"] = {};
+ for (let key in this.weightedWords) {
+ if (this.weightedWords.hasOwnProperty(key))
+ data["WeightedWords"][key] = this.weightedWords[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IRawDataResult extends IDistResult {
+ samples?: { [key: string]: any[]; } | undefined;
+ weightedWords?: { [key: string]: Word[]; } | undefined;
+}
+
+export class Word implements IWord {
+ text?: string | undefined;
+ occurrences?: number | undefined;
+ stem?: string | undefined;
+ isWordGroup?: boolean | undefined;
+
+ constructor(data?: IWord) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.text = data["Text"];
+ this.occurrences = data["Occurrences"];
+ this.stem = data["Stem"];
+ this.isWordGroup = data["IsWordGroup"];
+ }
+ }
+
+ static fromJS(data: any): Word {
+ data = typeof data === 'object' ? data : {};
+ let result = new Word();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Text"] = this.text;
+ data["Occurrences"] = this.occurrences;
+ data["Stem"] = this.stem;
+ data["IsWordGroup"] = this.isWordGroup;
+ return data;
+ }
+}
+
+export interface IWord {
+ text?: string | undefined;
+ occurrences?: number | undefined;
+ stem?: string | undefined;
+ isWordGroup?: boolean | undefined;
+}
+
+export class SampleOperationParameters extends DistOperationParameters implements ISampleOperationParameters {
+ numSamples?: number | undefined;
+ attributeParameters?: AttributeParameters[] | undefined;
+ brushes?: string[] | undefined;
+
+ constructor(data?: ISampleOperationParameters) {
+ super(data);
+ this._discriminator = "SampleOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.numSamples = data["NumSamples"];
+ if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) {
+ this.attributeParameters = [];
+ for (let item of data["AttributeParameters"])
+ this.attributeParameters.push(AttributeParameters.fromJS(item));
+ }
+ if (data["Brushes"] && data["Brushes"].constructor === Array) {
+ this.brushes = [];
+ for (let item of data["Brushes"])
+ this.brushes.push(item);
+ }
+ }
+ }
+
+ static fromJS(data: any): SampleOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new SampleOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["NumSamples"] = this.numSamples;
+ if (this.attributeParameters && this.attributeParameters.constructor === Array) {
+ data["AttributeParameters"] = [];
+ for (let item of this.attributeParameters)
+ data["AttributeParameters"].push(item.toJSON());
+ }
+ if (this.brushes && this.brushes.constructor === Array) {
+ data["Brushes"] = [];
+ for (let item of this.brushes)
+ data["Brushes"].push(item);
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISampleOperationParameters extends IDistOperationParameters {
+ numSamples?: number | undefined;
+ attributeParameters?: AttributeParameters[] | undefined;
+ brushes?: string[] | undefined;
+}
+
+export class SampleResult extends DistResult implements ISampleResult {
+ samples?: { [key: string]: { [key: string]: number; }; } | undefined;
+ isTruncated?: boolean | undefined;
+
+ constructor(data?: ISampleResult) {
+ super(data);
+ this._discriminator = "SampleResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Samples"]) {
+ this.samples = {};
+ for (let key in data["Samples"]) {
+ if (data["Samples"].hasOwnProperty(key))
+ this.samples[key] = data["Samples"][key] !== undefined ? data["Samples"][key] : {};
+ }
+ }
+ this.isTruncated = data["IsTruncated"];
+ }
+ }
+
+ static fromJS(data: any): SampleResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new SampleResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.samples) {
+ data["Samples"] = {};
+ for (let key in this.samples) {
+ if (this.samples.hasOwnProperty(key))
+ data["Samples"][key] = this.samples[key];
+ }
+ }
+ data["IsTruncated"] = this.isTruncated;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ISampleResult extends IDistResult {
+ samples?: { [key: string]: { [key: string]: number; }; } | undefined;
+ isTruncated?: boolean | undefined;
+}
+
+export class ResultParameters extends UniqueJson implements IResultParameters {
+ operationReference?: IOperationReference | undefined;
+ stopOperation?: boolean | undefined;
+
+ protected _discriminator: string;
+
+ constructor(data?: IResultParameters) {
+ super(data);
+ this._discriminator = "ResultParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.operationReference = data["OperationReference"] ? IOperationReference.fromJS(data["OperationReference"]) : <any>undefined;
+ this.stopOperation = data["StopOperation"];
+ }
+ }
+
+ static fromJS(data: any): ResultParameters {
+ data = typeof data === 'object' ? data : {};
+ if (data["discriminator"] === "RecommenderResultParameters") {
+ let result = new RecommenderResultParameters();
+ result.init(data);
+ return result;
+ }
+ let result = new ResultParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["discriminator"] = this._discriminator;
+ data["OperationReference"] = this.operationReference ? this.operationReference.toJSON() : <any>undefined;
+ data["StopOperation"] = this.stopOperation;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IResultParameters extends IUniqueJson {
+ operationReference?: IOperationReference | undefined;
+ stopOperation?: boolean | undefined;
+}
+
+export class RecommenderResultParameters extends ResultParameters implements IRecommenderResultParameters {
+ from?: number | undefined;
+ to?: number | undefined;
+ pValueSorting?: Sorting | undefined;
+ effectSizeFilter?: EffectSize | undefined;
+
+ constructor(data?: IRecommenderResultParameters) {
+ super(data);
+ this._discriminator = "RecommenderResultParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.from = data["From"];
+ this.to = data["To"];
+ this.pValueSorting = data["PValueSorting"];
+ this.effectSizeFilter = data["EffectSizeFilter"];
+ }
+ }
+
+ static fromJS(data: any): RecommenderResultParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new RecommenderResultParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["From"] = this.from;
+ data["To"] = this.to;
+ data["PValueSorting"] = this.pValueSorting;
+ data["EffectSizeFilter"] = this.effectSizeFilter;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IRecommenderResultParameters extends IResultParameters {
+ from?: number | undefined;
+ to?: number | undefined;
+ pValueSorting?: Sorting | undefined;
+ effectSizeFilter?: EffectSize | undefined;
+}
+
+export enum Sorting {
+ Ascending = "Ascending",
+ Descending = "Descending",
+}
+
+export class AddComparisonParameters extends ModelOperationParameters implements IAddComparisonParameters {
+ modelId?: ModelId | undefined;
+ comparisonOrder?: number | undefined;
+ childOperationParameters?: OperationParameters[] | undefined;
+ isCachable?: boolean | undefined;
+
+ constructor(data?: IAddComparisonParameters) {
+ super(data);
+ this._discriminator = "AddComparisonParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
+ this.comparisonOrder = data["ComparisonOrder"];
+ if (data["ChildOperationParameters"] && data["ChildOperationParameters"].constructor === Array) {
+ this.childOperationParameters = [];
+ for (let item of data["ChildOperationParameters"])
+ this.childOperationParameters.push(OperationParameters.fromJS(item));
+ }
+ this.isCachable = data["IsCachable"];
+ }
+ }
+
+ static fromJS(data: any): AddComparisonParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new AddComparisonParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
+ data["ComparisonOrder"] = this.comparisonOrder;
+ if (this.childOperationParameters && this.childOperationParameters.constructor === Array) {
+ data["ChildOperationParameters"] = [];
+ for (let item of this.childOperationParameters)
+ data["ChildOperationParameters"].push(item.toJSON());
+ }
+ data["IsCachable"] = this.isCachable;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IAddComparisonParameters extends IModelOperationParameters {
+ modelId?: ModelId | undefined;
+ comparisonOrder?: number | undefined;
+ childOperationParameters?: OperationParameters[] | undefined;
+ isCachable?: boolean | undefined;
+}
+
+export class CDFResult extends DistResult implements ICDFResult {
+ cDF?: { [key: string]: number; } | undefined;
+
+ constructor(data?: ICDFResult) {
+ super(data);
+ this._discriminator = "CDFResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["CDF"]) {
+ this.cDF = {};
+ for (let key in data["CDF"]) {
+ if (data["CDF"].hasOwnProperty(key))
+ this.cDF[key] = data["CDF"][key];
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): CDFResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new CDFResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.cDF) {
+ data["CDF"] = {};
+ for (let key in this.cDF) {
+ if (this.cDF.hasOwnProperty(key))
+ data["CDF"][key] = this.cDF[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ICDFResult extends IDistResult {
+ cDF?: { [key: string]: number; } | undefined;
+}
+
+export class ChiSquaredTestResult extends HypothesisTestResult implements IChiSquaredTestResult {
+ hs_aligned?: TupleOfDoubleAndDouble[] | undefined;
+
+ constructor(data?: IChiSquaredTestResult) {
+ super(data);
+ this._discriminator = "ChiSquaredTestResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["hs_aligned"] && data["hs_aligned"].constructor === Array) {
+ this.hs_aligned = [];
+ for (let item of data["hs_aligned"])
+ this.hs_aligned.push(TupleOfDoubleAndDouble.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): ChiSquaredTestResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new ChiSquaredTestResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.hs_aligned && this.hs_aligned.constructor === Array) {
+ data["hs_aligned"] = [];
+ for (let item of this.hs_aligned)
+ data["hs_aligned"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IChiSquaredTestResult extends IHypothesisTestResult {
+ hs_aligned?: TupleOfDoubleAndDouble[] | undefined;
+}
+
+export class CorrelationTestResult extends HypothesisTestResult implements ICorrelationTestResult {
+ degreeOfFreedom?: number | undefined;
+ sampleCorrelationCoefficient?: number | undefined;
+ distResult?: EmpiricalDistResult | undefined;
+
+ constructor(data?: ICorrelationTestResult) {
+ super(data);
+ this._discriminator = "CorrelationTestResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.degreeOfFreedom = data["DegreeOfFreedom"];
+ this.sampleCorrelationCoefficient = data["SampleCorrelationCoefficient"];
+ this.distResult = data["DistResult"] ? EmpiricalDistResult.fromJS(data["DistResult"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): CorrelationTestResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new CorrelationTestResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["DegreeOfFreedom"] = this.degreeOfFreedom;
+ data["SampleCorrelationCoefficient"] = this.sampleCorrelationCoefficient;
+ data["DistResult"] = this.distResult ? this.distResult.toJSON() : <any>undefined;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ICorrelationTestResult extends IHypothesisTestResult {
+ degreeOfFreedom?: number | undefined;
+ sampleCorrelationCoefficient?: number | undefined;
+ distResult?: EmpiricalDistResult | undefined;
+}
+
+export class EmpiricalDistResult extends DistResult implements IEmpiricalDistResult {
+ marginals?: AttributeParameters[] | undefined;
+ marginalDistParameters?: { [key: string]: DistParameter; } | undefined;
+ jointDistParameter?: JointDistParameter | undefined;
+
+ constructor(data?: IEmpiricalDistResult) {
+ super(data);
+ this._discriminator = "EmpiricalDistResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["Marginals"] && data["Marginals"].constructor === Array) {
+ this.marginals = [];
+ for (let item of data["Marginals"])
+ this.marginals.push(AttributeParameters.fromJS(item));
+ }
+ if (data["MarginalDistParameters"]) {
+ this.marginalDistParameters = {};
+ for (let key in data["MarginalDistParameters"]) {
+ if (data["MarginalDistParameters"].hasOwnProperty(key))
+ this.marginalDistParameters[key] = data["MarginalDistParameters"][key] ? DistParameter.fromJS(data["MarginalDistParameters"][key]) : new DistParameter();
+ }
+ }
+ this.jointDistParameter = data["JointDistParameter"] ? JointDistParameter.fromJS(data["JointDistParameter"]) : <any>undefined;
+ }
+ }
+
+ static fromJS(data: any): EmpiricalDistResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new EmpiricalDistResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.marginals && this.marginals.constructor === Array) {
+ data["Marginals"] = [];
+ for (let item of this.marginals)
+ data["Marginals"].push(item.toJSON());
+ }
+ if (this.marginalDistParameters) {
+ data["MarginalDistParameters"] = {};
+ for (let key in this.marginalDistParameters) {
+ if (this.marginalDistParameters.hasOwnProperty(key))
+ data["MarginalDistParameters"][key] = this.marginalDistParameters[key];
+ }
+ }
+ data["JointDistParameter"] = this.jointDistParameter ? this.jointDistParameter.toJSON() : <any>undefined;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IEmpiricalDistResult extends IDistResult {
+ marginals?: AttributeParameters[] | undefined;
+ marginalDistParameters?: { [key: string]: DistParameter; } | undefined;
+ jointDistParameter?: JointDistParameter | undefined;
+}
+
+export class DistParameter implements IDistParameter {
+ mean?: number | undefined;
+ moment2?: number | undefined;
+ variance?: number | undefined;
+ varianceEstimate?: number | undefined;
+ min?: number | undefined;
+ max?: number | undefined;
+
+ constructor(data?: IDistParameter) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.mean = data["Mean"];
+ this.moment2 = data["Moment2"];
+ this.variance = data["Variance"];
+ this.varianceEstimate = data["VarianceEstimate"];
+ this.min = data["Min"];
+ this.max = data["Max"];
+ }
+ }
+
+ static fromJS(data: any): DistParameter {
+ data = typeof data === 'object' ? data : {};
+ let result = new DistParameter();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Mean"] = this.mean;
+ data["Moment2"] = this.moment2;
+ data["Variance"] = this.variance;
+ data["VarianceEstimate"] = this.varianceEstimate;
+ data["Min"] = this.min;
+ data["Max"] = this.max;
+ return data;
+ }
+}
+
+export interface IDistParameter {
+ mean?: number | undefined;
+ moment2?: number | undefined;
+ variance?: number | undefined;
+ varianceEstimate?: number | undefined;
+ min?: number | undefined;
+ max?: number | undefined;
+}
+
+export class JointDistParameter implements IJointDistParameter {
+ jointDist?: DistParameter | undefined;
+ covariance?: number | undefined;
+
+ constructor(data?: IJointDistParameter) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.jointDist = data["JointDist"] ? DistParameter.fromJS(data["JointDist"]) : <any>undefined;
+ this.covariance = data["Covariance"];
+ }
+ }
+
+ static fromJS(data: any): JointDistParameter {
+ data = typeof data === 'object' ? data : {};
+ let result = new JointDistParameter();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["JointDist"] = this.jointDist ? this.jointDist.toJSON() : <any>undefined;
+ data["Covariance"] = this.covariance;
+ return data;
+ }
+}
+
+export interface IJointDistParameter {
+ jointDist?: DistParameter | undefined;
+ covariance?: number | undefined;
+}
+
+export enum DistributionType {
+ Continuous = 0,
+ Discrete = 1,
+}
+
+export abstract class DistributionTypeExtension implements IDistributionTypeExtension {
+
+ constructor(data?: IDistributionTypeExtension) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): DistributionTypeExtension {
+ data = typeof data === 'object' ? data : {};
+ throw new Error("The abstract class 'DistributionTypeExtension' cannot be instantiated.");
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ return data;
+ }
+}
+
+export interface IDistributionTypeExtension {
+}
+
+export class GetModelStateParameters extends ModelOperationParameters implements IGetModelStateParameters {
+ modelId?: ModelId | undefined;
+ comparisonIds?: ComparisonId[] | undefined;
+ riskControlType?: RiskControlType | undefined;
+
+ constructor(data?: IGetModelStateParameters) {
+ super(data);
+ this._discriminator = "GetModelStateParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
+ if (data["ComparisonIds"] && data["ComparisonIds"].constructor === Array) {
+ this.comparisonIds = [];
+ for (let item of data["ComparisonIds"])
+ this.comparisonIds.push(ComparisonId.fromJS(item));
+ }
+ this.riskControlType = data["RiskControlType"];
+ }
+ }
+
+ static fromJS(data: any): GetModelStateParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new GetModelStateParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
+ if (this.comparisonIds && this.comparisonIds.constructor === Array) {
+ data["ComparisonIds"] = [];
+ for (let item of this.comparisonIds)
+ data["ComparisonIds"].push(item.toJSON());
+ }
+ data["RiskControlType"] = this.riskControlType;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IGetModelStateParameters extends IModelOperationParameters {
+ modelId?: ModelId | undefined;
+ comparisonIds?: ComparisonId[] | undefined;
+ riskControlType?: RiskControlType | undefined;
+}
+
+export class KSTestResult extends HypothesisTestResult implements IKSTestResult {
+
+ constructor(data?: IKSTestResult) {
+ super(data);
+ this._discriminator = "KSTestResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): KSTestResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new KSTestResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IKSTestResult extends IHypothesisTestResult {
+}
+
+export class ModelWealthParameters extends UniqueJson implements IModelWealthParameters {
+ modelId?: ModelId | undefined;
+ riskControlType?: RiskControlType | undefined;
+
+ constructor(data?: IModelWealthParameters) {
+ super(data);
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.modelId = data["ModelId"] ? ModelId.fromJS(data["ModelId"]) : <any>undefined;
+ this.riskControlType = data["RiskControlType"];
+ }
+ }
+
+ static fromJS(data: any): ModelWealthParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new ModelWealthParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["ModelId"] = this.modelId ? this.modelId.toJSON() : <any>undefined;
+ data["RiskControlType"] = this.riskControlType;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IModelWealthParameters extends IUniqueJson {
+ modelId?: ModelId | undefined;
+ riskControlType?: RiskControlType | undefined;
+}
+
+export class RootMeanSquareTestResult extends HypothesisTestResult implements IRootMeanSquareTestResult {
+ simulationCount?: number | undefined;
+ extremeSimulationCount?: number | undefined;
+
+ constructor(data?: IRootMeanSquareTestResult) {
+ super(data);
+ this._discriminator = "RootMeanSquareTestResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.simulationCount = data["SimulationCount"];
+ this.extremeSimulationCount = data["ExtremeSimulationCount"];
+ }
+ }
+
+ static fromJS(data: any): RootMeanSquareTestResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new RootMeanSquareTestResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["SimulationCount"] = this.simulationCount;
+ data["ExtremeSimulationCount"] = this.extremeSimulationCount;
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IRootMeanSquareTestResult extends IHypothesisTestResult {
+ simulationCount?: number | undefined;
+ extremeSimulationCount?: number | undefined;
+}
+
+export class TTestResult extends HypothesisTestResult implements ITTestResult {
+ degreeOfFreedom?: number | undefined;
+ distResults?: EmpiricalDistResult[] | undefined;
+
+ constructor(data?: ITTestResult) {
+ super(data);
+ this._discriminator = "TTestResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.degreeOfFreedom = data["DegreeOfFreedom"];
+ if (data["DistResults"] && data["DistResults"].constructor === Array) {
+ this.distResults = [];
+ for (let item of data["DistResults"])
+ this.distResults.push(EmpiricalDistResult.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): TTestResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new TTestResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["DegreeOfFreedom"] = this.degreeOfFreedom;
+ if (this.distResults && this.distResults.constructor === Array) {
+ data["DistResults"] = [];
+ for (let item of this.distResults)
+ data["DistResults"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface ITTestResult extends IHypothesisTestResult {
+ degreeOfFreedom?: number | undefined;
+ distResults?: EmpiricalDistResult[] | undefined;
+}
+
+export enum Sorting2 {
+ Ascending = 0,
+ Descending = 1,
+}
+
+export class BinLabel implements IBinLabel {
+ value?: number | undefined;
+ minValue?: number | undefined;
+ maxValue?: number | undefined;
+ label?: string | undefined;
+
+ constructor(data?: IBinLabel) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.value = data["Value"];
+ this.minValue = data["MinValue"];
+ this.maxValue = data["MaxValue"];
+ this.label = data["Label"];
+ }
+ }
+
+ static fromJS(data: any): BinLabel {
+ data = typeof data === 'object' ? data : {};
+ let result = new BinLabel();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ data["MinValue"] = this.minValue;
+ data["MaxValue"] = this.maxValue;
+ data["Label"] = this.label;
+ return data;
+ }
+}
+
+export interface IBinLabel {
+ value?: number | undefined;
+ minValue?: number | undefined;
+ maxValue?: number | undefined;
+ label?: string | undefined;
+}
+
+export class PreProcessedString implements IPreProcessedString {
+ value?: string | undefined;
+ id?: number | undefined;
+ stringLookup?: { [key: string]: number; } | undefined;
+ indexLookup?: { [key: string]: string; } | undefined;
+
+ constructor(data?: IPreProcessedString) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.value = data["Value"];
+ this.id = data["Id"];
+ if (data["StringLookup"]) {
+ this.stringLookup = {};
+ for (let key in data["StringLookup"]) {
+ if (data["StringLookup"].hasOwnProperty(key))
+ this.stringLookup[key] = data["StringLookup"][key];
+ }
+ }
+ if (data["IndexLookup"]) {
+ this.indexLookup = {};
+ for (let key in data["IndexLookup"]) {
+ if (data["IndexLookup"].hasOwnProperty(key))
+ this.indexLookup[key] = data["IndexLookup"][key];
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): PreProcessedString {
+ data = typeof data === 'object' ? data : {};
+ let result = new PreProcessedString();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Value"] = this.value;
+ data["Id"] = this.id;
+ if (this.stringLookup) {
+ data["StringLookup"] = {};
+ for (let key in this.stringLookup) {
+ if (this.stringLookup.hasOwnProperty(key))
+ data["StringLookup"][key] = this.stringLookup[key];
+ }
+ }
+ if (this.indexLookup) {
+ data["IndexLookup"] = {};
+ for (let key in this.indexLookup) {
+ if (this.indexLookup.hasOwnProperty(key))
+ data["IndexLookup"][key] = this.indexLookup[key];
+ }
+ }
+ return data;
+ }
+}
+
+export interface IPreProcessedString {
+ value?: string | undefined;
+ id?: number | undefined;
+ stringLookup?: { [key: string]: number; } | undefined;
+ indexLookup?: { [key: string]: string; } | undefined;
+}
+
+export class BitSet implements IBitSet {
+ length?: number | undefined;
+ size?: number | undefined;
+
+ constructor(data?: IBitSet) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ this.length = data["Length"];
+ this.size = data["Size"];
+ }
+ }
+
+ static fromJS(data: any): BitSet {
+ data = typeof data === 'object' ? data : {};
+ let result = new BitSet();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Length"] = this.length;
+ data["Size"] = this.size;
+ return data;
+ }
+}
+
+export interface IBitSet {
+ length?: number | undefined;
+ size?: number | undefined;
+}
+
+export class DateTimeUtil implements IDateTimeUtil {
+
+ constructor(data?: IDateTimeUtil) {
+ if (data) {
+ for (var property in data) {
+ if (data.hasOwnProperty(property))
+ (<any>this)[property] = (<any>data)[property];
+ }
+ }
+ }
+
+ init(data?: any) {
+ if (data) {
+ }
+ }
+
+ static fromJS(data: any): DateTimeUtil {
+ data = typeof data === 'object' ? data : {};
+ let result = new DateTimeUtil();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ return data;
+ }
+}
+
+export interface IDateTimeUtil {
+}
+
+export class FrequentItemsetOperationParameters extends DistOperationParameters implements IFrequentItemsetOperationParameters {
+ filter?: string | undefined;
+ attributeParameters?: AttributeParameters[] | undefined;
+ attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined;
+
+ constructor(data?: IFrequentItemsetOperationParameters) {
+ super(data);
+ this._discriminator = "FrequentItemsetOperationParameters";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ this.filter = data["Filter"];
+ if (data["AttributeParameters"] && data["AttributeParameters"].constructor === Array) {
+ this.attributeParameters = [];
+ for (let item of data["AttributeParameters"])
+ this.attributeParameters.push(AttributeParameters.fromJS(item));
+ }
+ if (data["AttributeCodeParameters"] && data["AttributeCodeParameters"].constructor === Array) {
+ this.attributeCodeParameters = [];
+ for (let item of data["AttributeCodeParameters"])
+ this.attributeCodeParameters.push(AttributeCaclculatedParameters.fromJS(item));
+ }
+ }
+ }
+
+ static fromJS(data: any): FrequentItemsetOperationParameters {
+ data = typeof data === 'object' ? data : {};
+ let result = new FrequentItemsetOperationParameters();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["Filter"] = this.filter;
+ if (this.attributeParameters && this.attributeParameters.constructor === Array) {
+ data["AttributeParameters"] = [];
+ for (let item of this.attributeParameters)
+ data["AttributeParameters"].push(item.toJSON());
+ }
+ if (this.attributeCodeParameters && this.attributeCodeParameters.constructor === Array) {
+ data["AttributeCodeParameters"] = [];
+ for (let item of this.attributeCodeParameters)
+ data["AttributeCodeParameters"].push(item.toJSON());
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IFrequentItemsetOperationParameters extends IDistOperationParameters {
+ filter?: string | undefined;
+ attributeParameters?: AttributeParameters[] | undefined;
+ attributeCodeParameters?: AttributeCaclculatedParameters[] | undefined;
+}
+
+export class FrequentItemsetResult extends Result implements IFrequentItemsetResult {
+ frequentItems?: { [key: string]: number; } | undefined;
+
+ constructor(data?: IFrequentItemsetResult) {
+ super(data);
+ this._discriminator = "FrequentItemsetResult";
+ }
+
+ init(data?: any) {
+ super.init(data);
+ if (data) {
+ if (data["FrequentItems"]) {
+ this.frequentItems = {};
+ for (let key in data["FrequentItems"]) {
+ if (data["FrequentItems"].hasOwnProperty(key))
+ this.frequentItems[key] = data["FrequentItems"][key];
+ }
+ }
+ }
+ }
+
+ static fromJS(data: any): FrequentItemsetResult {
+ data = typeof data === 'object' ? data : {};
+ let result = new FrequentItemsetResult();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ if (this.frequentItems) {
+ data["FrequentItems"] = {};
+ for (let key in this.frequentItems) {
+ if (this.frequentItems.hasOwnProperty(key))
+ data["FrequentItems"][key] = this.frequentItems[key];
+ }
+ }
+ super.toJSON(data);
+ return data;
+ }
+}
+
+export interface IFrequentItemsetResult extends IResult {
+ frequentItems?: { [key: string]: number; } | undefined;
+}
+
diff --git a/src/client/northstar/operations/BaseOperation.ts b/src/client/northstar/operations/BaseOperation.ts
new file mode 100644
index 000000000..0d1361ebf
--- /dev/null
+++ b/src/client/northstar/operations/BaseOperation.ts
@@ -0,0 +1,162 @@
+import { FilterModel } from '../core/filter/FilterModel';
+import { ErrorResult, Exception, OperationParameters, OperationReference, Result, ResultParameters } from '../model/idea/idea';
+import { action, computed, observable } from "mobx";
+import { Gateway } from '../manager/Gateway';
+
+export abstract class BaseOperation {
+ private _interactionTimeoutId: number = 0;
+ private static _currentOperations: Map<number, PollPromise> = new Map<number, PollPromise>();
+ //public InteractionTimeout: EventDelegate<InteractionTimeoutEventArgs> = new EventDelegate<InteractionTimeoutEventArgs>();
+
+ @observable public Error: string = "";
+ @observable public OverridingFilters: FilterModel[] = [];
+ //@observable
+ @observable public Result?: Result = undefined;
+ @observable public ComputationStarted: boolean = false;
+ public OperationReference?: OperationReference = undefined;
+
+ private static _nextId = 0;
+ public RequestSalt: string = "";
+ public Id: number;
+
+ constructor() {
+ this.Id = BaseOperation._nextId++;
+ }
+
+ @computed
+ public get FilterString(): string {
+ return "";
+ }
+
+
+ @action
+ public SetResult(result: Result): void {
+ this.Result = result;
+ }
+
+ public async Update(): Promise<void> {
+
+ try {
+ if (BaseOperation._currentOperations.has(this.Id)) {
+ BaseOperation._currentOperations.get(this.Id)!.Cancel();
+ if (this.OperationReference) {
+ Gateway.Instance.PauseOperation(this.OperationReference.toJSON());
+ }
+ }
+
+ let operationParameters = this.CreateOperationParameters();
+ if (this.Result) {
+ this.Result.progress = 0;
+ } // bcz: used to set Result to undefined, but that causes the display to blink
+ this.Error = "";
+ let salt = Math.random().toString();
+ this.RequestSalt = salt;
+
+ if (!operationParameters) {
+ this.ComputationStarted = false;
+ return;
+ }
+
+ this.ComputationStarted = true;
+ //let start = performance.now();
+ let promise = Gateway.Instance.StartOperation(operationParameters.toJSON());
+ promise.catch(err => {
+ action(() => {
+ this.Error = err;
+ console.error(err);
+ });
+ });
+ let operationReference = await promise;
+
+
+ if (operationReference) {
+ this.OperationReference = operationReference;
+
+ let resultParameters = new ResultParameters();
+ resultParameters.operationReference = operationReference;
+
+ let pollPromise = new PollPromise(salt, operationReference);
+ BaseOperation._currentOperations.set(this.Id, pollPromise);
+
+ pollPromise.Start(async () => {
+ let result = await Gateway.Instance.GetResult(resultParameters.toJSON());
+ if (result instanceof ErrorResult) {
+ throw new Error((result).message);
+ }
+ if (this.RequestSalt === pollPromise.RequestSalt) {
+ if (result && (!this.Result || this.Result.progress !== result.progress)) {
+ /*if (operationViewModel.Result !== null && operationViewModel.Result !== undefined) {
+ let t1 = performance.now();
+ console.log((t1 - start) + " milliseconds.");
+ start = performance.now();
+ }*/
+ this.SetResult(result);
+ }
+
+ if (!result || result.progress! < 1) {
+ return true;
+ }
+ }
+ return false;
+ }, 100).catch((err: Error) => action(() => {
+ this.Error = err.message;
+ console.error(err.message);
+ })()
+ );
+ }
+ }
+ catch (err) {
+ console.error(err as Exception);
+ // ErrorDialog.Instance.HandleError(err, operationViewModel);
+ }
+ }
+
+ public CreateOperationParameters(): OperationParameters | undefined { return undefined; }
+
+ private interactionTimeout() {
+ // clearTimeout(this._interactionTimeoutId);
+ // this.InteractionTimeout.Fire(new InteractionTimeoutEventArgs(this.TypedViewModel, InteractionTimeoutType.Timeout));
+ }
+}
+
+export class PollPromise {
+ public RequestSalt: string;
+ public OperationReference: OperationReference;
+
+ private _notCanceled: boolean = true;
+ private _poll: undefined | (() => Promise<boolean>);
+ private _delay: number = 0;
+
+ public constructor(requestKey: string, operationReference: OperationReference) {
+ this.RequestSalt = requestKey;
+ this.OperationReference = operationReference;
+ }
+
+ public Cancel(): void {
+ this._notCanceled = false;
+ }
+
+ public Start(poll: () => Promise<boolean>, delay: number): Promise<void> {
+ this._poll = poll;
+ this._delay = delay;
+ return this.pollRecursive();
+ }
+
+ private pollRecursive = (): Promise<void> => {
+ return Promise.resolve().then(this._poll).then((flag) => {
+ this._notCanceled && flag && new Promise((res) => (setTimeout(res, this._delay)))
+ .then(this.pollRecursive);
+ });
+ }
+}
+
+
+export class InteractionTimeoutEventArgs {
+ constructor(public Sender: object, public Type: InteractionTimeoutType) {
+ }
+}
+
+export enum InteractionTimeoutType {
+ Reset = 0,
+ Timeout = 1
+}
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
new file mode 100644
index 000000000..74e23ea48
--- /dev/null
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -0,0 +1,158 @@
+import { action, computed, observable, trace } from "mobx";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { ColumnAttributeModel } from "../core/attribute/AttributeModel";
+import { AttributeTransformationModel } from "../core/attribute/AttributeTransformationModel";
+import { CalculatedAttributeManager } from "../core/attribute/CalculatedAttributeModel";
+import { FilterModel } from "../core/filter/FilterModel";
+import { FilterOperand } from "../core/filter/FilterOperand";
+import { IBaseFilterConsumer } from "../core/filter/IBaseFilterConsumer";
+import { IBaseFilterProvider } from "../core/filter/IBaseFilterProvider";
+import { HistogramField } from "../dash-fields/HistogramField";
+import { SETTINGS_SAMPLE_SIZE, SETTINGS_X_BINS, SETTINGS_Y_BINS } from "../model/binRanges/VisualBinRangeHelper";
+import { AggregateFunction, AggregateParameters, Attribute, AverageAggregateParameters, Bin, DataType, DoubleValueAggregateResult, HistogramOperationParameters, HistogramResult, QuantitativeBinRange } from "../model/idea/idea";
+import { ModelHelpers } from "../model/ModelHelpers";
+import { ArrayUtil } from "../utils/ArrayUtil";
+import { BaseOperation } from "./BaseOperation";
+import { Doc } from "../../../new_fields/Doc";
+import { Cast, NumCast } from "../../../new_fields/Types";
+
+export class HistogramOperation extends BaseOperation implements IBaseFilterConsumer, IBaseFilterProvider {
+ public static Empty = new HistogramOperation("-empty schema-", new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())), new AttributeTransformationModel(new ColumnAttributeModel(new Attribute())));
+ @observable public FilterOperand: FilterOperand = FilterOperand.AND;
+ @observable public Links: Doc[] = [];
+ @observable public BrushLinks: { l: Doc, b: Doc }[] = [];
+ @observable public BrushColors: number[] = [];
+ @observable public BarFilterModels: FilterModel[] = [];
+
+ @observable public Normalization: number = -1;
+ @observable public X: AttributeTransformationModel;
+ @observable public Y: AttributeTransformationModel;
+ @observable public V: AttributeTransformationModel;
+ @observable public SchemaName: string;
+ @observable public QRange: QuantitativeBinRange | undefined;
+ public get Schema() { return CurrentUserUtils.GetNorthstarSchema(this.SchemaName); }
+
+ constructor(schemaName: string, x: AttributeTransformationModel, y: AttributeTransformationModel, v: AttributeTransformationModel, normalized?: number) {
+ super();
+ this.X = x;
+ this.Y = y;
+ this.V = v;
+ this.Normalization = normalized ? normalized : -1;
+ this.SchemaName = schemaName;
+ }
+
+ public static Duplicate(op: HistogramOperation) {
+
+ return new HistogramOperation(op.SchemaName, op.X, op.Y, op.V, op.Normalization);
+ }
+ public Copy(): HistogramOperation {
+ return new HistogramOperation(this.SchemaName, this.X, this.Y, this.V, this.Normalization);
+ }
+
+ Equals(other: Object): boolean {
+ throw new Error("Method not implemented.");
+ }
+
+
+ public get FilterModels() {
+ return this.BarFilterModels;
+ }
+ @action
+ public AddFilterModels(filterModels: FilterModel[]): void {
+ filterModels.filter(f => f !== null).forEach(fm => this.BarFilterModels.push(fm));
+ }
+ @action
+ public RemoveFilterModels(filterModels: FilterModel[]): void {
+ ArrayUtil.RemoveMany(this.BarFilterModels, filterModels);
+ }
+
+ @computed
+ public get FilterString(): string {
+ if (this.OverridingFilters.length > 0) {
+ return "(" + this.OverridingFilters.filter(fm => fm !== null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+ }
+ let filterModels: FilterModel[] = [];
+ return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
+ }
+
+ public get BrushString(): string[] {
+ let brushes: string[] = [];
+ this.BrushLinks.map(brushLink => {
+ let brushHistogram = Cast(brushLink.b.data, HistogramField);
+ if (brushHistogram) {
+ let filterModels: FilterModel[] = [];
+ brushes.push(FilterModel.GetFilterModelsRecursive(brushHistogram.HistoOp, new Set<IBaseFilterProvider>(), filterModels, false));
+ }
+ });
+ return brushes;
+ }
+
+ _stackedFilters: (FilterModel[])[] = [];
+ @action
+ public DrillDown(up: boolean) {
+ if (!up) {
+ if (!this.BarFilterModels.length) {
+ return;
+ }
+ this._stackedFilters.push(this.BarFilterModels.map(f => f));
+ this.OverridingFilters.length = 0;
+ this.OverridingFilters.push(...this._stackedFilters[this._stackedFilters.length - 1]);
+ this.BarFilterModels.map(fm => fm).map(fm => this.RemoveFilterModels([fm]));
+ //this.updateHistogram();
+ } else {
+ this.OverridingFilters.length = 0;
+ if (this._stackedFilters.length) {
+ this.OverridingFilters.push(...this._stackedFilters.pop()!);
+ }
+ // else
+ // this.updateHistogram();
+ }
+ }
+
+ private getAggregateParameters(histoX: AttributeTransformationModel, histoY: AttributeTransformationModel, histoValue: AttributeTransformationModel) {
+ let allAttributes = new Array<AttributeTransformationModel>(histoX, histoY, histoValue);
+ allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None));
+
+ let numericDataTypes = [DataType.Int, DataType.Double, DataType.Float];
+ let perBinAggregateParameters: AggregateParameters[] = ModelHelpers.GetAggregateParametersWithMargins(this.Schema!.distinctAttributeParameters, allAttributes);
+ let globalAggregateParameters: AggregateParameters[] = [];
+ [histoX, histoY]
+ .filter(a => a.AggregateFunction === AggregateFunction.None && ArrayUtil.Contains(numericDataTypes, a.AttributeModel.DataType))
+ .forEach(a => {
+ let avg = new AverageAggregateParameters();
+ avg.attributeParameters = ModelHelpers.GetAttributeParameters(a.AttributeModel);
+ globalAggregateParameters.push(avg);
+ });
+ return [perBinAggregateParameters, globalAggregateParameters];
+ }
+
+ public CreateOperationParameters(): HistogramOperationParameters | undefined {
+ if (this.X && this.Y && this.V) {
+ let [perBinAggregateParameters, globalAggregateParameters] = this.getAggregateParameters(this.X, this.Y, this.V);
+ return new HistogramOperationParameters({
+ enableBrushComputation: true,
+ adapterName: this.SchemaName,
+ filter: this.FilterString,
+ brushes: this.BrushString,
+ binningParameters: [ModelHelpers.GetBinningParameters(this.X, SETTINGS_X_BINS, this.QRange ? this.QRange.minValue : undefined, this.QRange ? this.QRange.maxValue : undefined),
+ ModelHelpers.GetBinningParameters(this.Y, SETTINGS_Y_BINS)],
+ sampleStreamBlockSize: SETTINGS_SAMPLE_SIZE,
+ perBinAggregateParameters: perBinAggregateParameters,
+ globalAggregateParameters: globalAggregateParameters,
+ sortPerBinAggregateParameter: undefined,
+ attributeCalculatedParameters: CalculatedAttributeManager
+ .AllCalculatedAttributes.map(a => ModelHelpers.GetAttributeParametersFromAttributeModel(a)),
+ degreeOfParallism: 1, // Settings.Instance.DegreeOfParallelism,
+ isCachable: false
+ });
+ }
+ }
+
+ @action
+ public async Update(): Promise<void> {
+ this.BrushColors = this.BrushLinks.map(e => NumCast(e.l.backgroundColor));
+ return super.Update();
+ }
+}
+
+
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts
new file mode 100644
index 000000000..12b8d8e77
--- /dev/null
+++ b/src/client/northstar/utils/ArrayUtil.ts
@@ -0,0 +1,90 @@
+import { Exception } from "../model/idea/idea";
+
+export class ArrayUtil {
+
+ public static Contains(arr1: any[], arr2: any): boolean {
+ if (arr1.length === 0) {
+ return false;
+ }
+ let isComplex = typeof arr1[0] === "object";
+ for (const ele of arr1) {
+ if (isComplex && "Equals" in ele) {
+ if (ele.Equals(arr2)) {
+ return true;
+ }
+ }
+ else {
+ if (ele === arr2) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ public static RemoveMany(arr: any[], elements: Object[]) {
+ elements.forEach(e => ArrayUtil.Remove(arr, e));
+ }
+
+ public static AddMany(arr: any[], others: Object[]) {
+ arr.push(...others);
+ }
+
+ public static Clear(arr: any[]) {
+ arr.splice(0, arr.length);
+ }
+
+
+ public static Remove(arr: any[], other: Object) {
+ const index = ArrayUtil.IndexOfWithEqual(arr, other);
+ if (index === -1) {
+ return;
+ }
+ arr.splice(index, 1);
+ }
+
+
+ public static First<T>(arr: T[], predicate: (x: any) => boolean): T {
+ let filtered = arr.filter(predicate);
+ if (filtered.length > 0) {
+ return filtered[0];
+ }
+ throw new Exception();
+ }
+
+ public static FirstOrDefault<T>(arr: T[], predicate: (x: any) => boolean): T | undefined {
+ let filtered = arr.filter(predicate);
+ if (filtered.length > 0) {
+ return filtered[0];
+ }
+ return undefined;
+ }
+
+ public static Distinct(arr: any[]): any[] {
+ let ret = [];
+ for (const ele of arr) {
+ if (!ArrayUtil.Contains(ret, ele)) {
+ ret.push(ele);
+ }
+ }
+ return ret;
+ }
+
+ public static IndexOfWithEqual(arr: any[], other: any): number {
+ for (let i = 0; i < arr.length; i++) {
+ let isComplex = typeof arr[0] === "object";
+ if (isComplex && "Equals" in arr[i]) {
+ if (arr[i].Equals(other)) {
+ return i;
+ }
+ }
+ else {
+ if (arr[i] === other) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts
new file mode 100644
index 000000000..df14d4da0
--- /dev/null
+++ b/src/client/northstar/utils/Extensions.ts
@@ -0,0 +1,29 @@
+interface String {
+ ReplaceAll(toReplace: string, replacement: string): string;
+ Truncate(length: number, replacement: string): String;
+}
+
+String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string {
+ var target = this;
+ return target.split(toReplace).join(replacement);
+};
+
+String.prototype.Truncate = function (length: number, replacement: string): String {
+ var target = this;
+ if (target.length >= length) {
+ target = target.slice(0, Math.max(0, length - replacement.length)) + replacement;
+ }
+ return target;
+};
+
+interface Math {
+ log10(val: number): number;
+}
+
+Math.log10 = function (val: number): number {
+ return Math.log(val) / Math.LN10;
+};
+
+declare interface ObjectConstructor {
+ assign(...objects: Object[]): Object;
+}
diff --git a/src/client/northstar/utils/GeometryUtil.ts b/src/client/northstar/utils/GeometryUtil.ts
new file mode 100644
index 000000000..d5220c479
--- /dev/null
+++ b/src/client/northstar/utils/GeometryUtil.ts
@@ -0,0 +1,133 @@
+import { MathUtil, PIXIRectangle, PIXIPoint } from "./MathUtil";
+
+
+export class GeometryUtil {
+
+ public static ComputeBoundingBox(points: { x: number, y: number }[], scale = 1, padding: number = 0): PIXIRectangle {
+ let minX: number = Number.MAX_VALUE;
+ let minY: number = Number.MAX_VALUE;
+ let maxX: number = Number.MIN_VALUE;
+ let maxY: number = Number.MIN_VALUE;
+ for (const point of points) {
+ if (point.x < minX) {
+ minX = point.x;
+ }
+ if (point.y < minY) {
+ minY = point.y;
+ }
+ if (point.x > maxX) {
+ maxX = point.x;
+ }
+ if (point.y > maxY) {
+ maxY = point.y;
+ }
+ }
+ return new PIXIRectangle(minX * scale - padding, minY * scale - padding, (maxX - minX) * scale + padding * 2, (maxY - minY) * scale + padding * 2);
+ }
+
+ public static RectangleOverlap(rect1: PIXIRectangle, rect2: PIXIRectangle) {
+ let x_overlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left));
+ let y_overlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top));
+ return x_overlap * y_overlap;
+ }
+
+ public static RotatePoints(center: { x: number, y: number }, points: { x: number, y: number }[], angle: number): PIXIPoint[] {
+ const rotate = (cx: number, cy: number, x: number, y: number, angle: number) => {
+ const radians = angle,
+ cos = Math.cos(radians),
+ sin = Math.sin(radians),
+ nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
+ ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
+ return new PIXIPoint(nx, ny);
+ };
+ return points.map(p => rotate(center.x, center.y, p.x, p.y, angle));
+ }
+
+ public static LineByLeastSquares(points: { x: number, y: number }[]): PIXIPoint[] {
+ let sum_x: number = 0;
+ let sum_y: number = 0;
+ let sum_xy: number = 0;
+ let sum_xx: number = 0;
+ let count: number = 0;
+
+ let x: number = 0;
+ let y: number = 0;
+
+
+ if (points.length === 0) {
+ return [];
+ }
+
+ for (const point of points) {
+ x = point.x;
+ y = point.y;
+ sum_x += x;
+ sum_y += y;
+ sum_xx += x * x;
+ sum_xy += x * y;
+ count++;
+ }
+
+ let m = (count * sum_xy - sum_x * sum_y) / (count * sum_xx - sum_x * sum_x);
+ let b = (sum_y / count) - (m * sum_x) / count;
+ let result: PIXIPoint[] = new Array<PIXIPoint>();
+
+ for (const point of points) {
+ x = point.x;
+ y = x * m + b;
+ result.push(new PIXIPoint(x, y));
+ }
+ return result;
+ }
+
+ // public static PointInsidePolygon(vs:Point[], x:number, y:number):boolean {
+ // // ray-casting algorithm based on
+ // // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+
+ // var inside = false;
+ // for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
+ // var xi = vs[i].x, yi = vs[i].y;
+ // var xj = vs[j].x, yj = vs[j].y;
+
+ // var intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
+ // if (intersect)
+ // inside = !inside;
+ // }
+
+ // return inside;
+ // };
+
+ public static IntersectLines(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number): boolean {
+ let a1: number, a2: number, b1: number, b2: number, c1: number, c2: number;
+ let r1: number, r2: number, r3: number, r4: number;
+ let denom: number, offset: number, num: number;
+
+ a1 = y2 - y1;
+ b1 = x1 - x2;
+ c1 = (x2 * y1) - (x1 * y2);
+ r3 = ((a1 * x3) + (b1 * y3) + c1);
+ r4 = ((a1 * x4) + (b1 * y4) + c1);
+
+ if ((r3 !== 0) && (r4 !== 0) && (MathUtil.Sign(r3) === MathUtil.Sign(r4))) {
+ return false;
+ }
+
+ a2 = y4 - y3;
+ b2 = x3 - x4;
+ c2 = (x4 * y3) - (x3 * y4);
+
+ r1 = (a2 * x1) + (b2 * y1) + c2;
+ r2 = (a2 * x2) + (b2 * y2) + c2;
+
+ if ((r1 !== 0) && (r2 !== 0) && (MathUtil.Sign(r1) === MathUtil.Sign(r2))) {
+ return false;
+ }
+
+ denom = (a1 * b2) - (a2 * b1);
+
+ if (denom === 0) {
+ return false;
+ }
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/IDisposable.ts b/src/client/northstar/utils/IDisposable.ts
new file mode 100644
index 000000000..5e9843326
--- /dev/null
+++ b/src/client/northstar/utils/IDisposable.ts
@@ -0,0 +1,3 @@
+export interface IDisposable {
+ Dispose(): void;
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/IEquatable.ts b/src/client/northstar/utils/IEquatable.ts
new file mode 100644
index 000000000..2f81c2478
--- /dev/null
+++ b/src/client/northstar/utils/IEquatable.ts
@@ -0,0 +1,3 @@
+export interface IEquatable {
+ Equals(other: Object): boolean;
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/KeyCodes.ts b/src/client/northstar/utils/KeyCodes.ts
new file mode 100644
index 000000000..044569ffe
--- /dev/null
+++ b/src/client/northstar/utils/KeyCodes.ts
@@ -0,0 +1,137 @@
+/**
+ * Class contains the keycodes for keys on your keyboard.
+ *
+ * Useful for auto completion:
+ *
+ * ```
+ * switch (event.key)
+ * {
+ * case KeyCode.UP:
+ * {
+ * // Up key pressed
+ * break;
+ * }
+ * case KeyCode.DOWN:
+ * {
+ * // Down key pressed
+ * break;
+ * }
+ * case KeyCode.LEFT:
+ * {
+ * // Left key pressed
+ * break;
+ * }
+ * case KeyCode.RIGHT:
+ * {
+ * // Right key pressed
+ * break;
+ * }
+ * default:
+ * {
+ * // ignore
+ * break;
+ * }
+ * }
+ * ```
+ */
+export class KeyCodes
+{
+ public static TAB:number = 9;
+ public static CAPS_LOCK:number = 20;
+ public static SHIFT:number = 16;
+ public static CONTROL:number = 17;
+ public static SPACE:number = 32;
+ public static DOWN:number = 40;
+ public static UP:number = 38;
+ public static LEFT:number = 37;
+ public static RIGHT:number = 39;
+ public static ESCAPE:number = 27;
+ public static F1:number = 112;
+ public static F2:number = 113;
+ public static F3:number = 114;
+ public static F4:number = 115;
+ public static F5:number = 116;
+ public static F6:number = 117;
+ public static F7:number = 118;
+ public static F8:number = 119;
+ public static F9:number = 120;
+ public static F10:number = 121;
+ public static F11:number = 122;
+ public static F12:number = 123;
+ public static INSERT:number = 45;
+ public static HOME:number = 36;
+ public static PAGE_UP:number = 33;
+ public static PAGE_DOWN:number = 34;
+ public static DELETE:number = 46;
+ public static END:number = 35;
+ public static ENTER:number = 13;
+ public static BACKSPACE:number = 8;
+ public static NUMPAD_0:number = 96;
+ public static NUMPAD_1:number = 97;
+ public static NUMPAD_2:number = 98;
+ public static NUMPAD_3:number = 99;
+ public static NUMPAD_4:number = 100;
+ public static NUMPAD_5:number = 101;
+ public static NUMPAD_6:number = 102;
+ public static NUMPAD_7:number = 103;
+ public static NUMPAD_8:number = 104;
+ public static NUMPAD_9:number = 105;
+ public static NUMPAD_DIVIDE:number = 111;
+ public static NUMPAD_ADD:number = 107;
+ public static NUMPAD_ENTER:number = 13;
+ public static NUMPAD_DECIMAL:number = 110;
+ public static NUMPAD_SUBTRACT:number = 109;
+ public static NUMPAD_MULTIPLY:number = 106;
+ public static SEMICOLON:number = 186;
+ public static EQUAL:number = 187;
+ public static COMMA:number = 188;
+ public static MINUS:number = 189;
+ public static PERIOD:number = 190;
+ public static SLASH:number = 191;
+ public static BACKQUOTE:number = 192;
+ public static LEFTBRACKET:number = 219;
+ public static BACKSLASH:number = 220;
+ public static RIGHTBRACKET:number = 221;
+ public static QUOTE:number = 222;
+ public static ALT:number = 18;
+ public static COMMAND:number = 15;
+ public static NUMPAD:number = 21;
+ public static A:number = 65;
+ public static B:number = 66;
+ public static C:number = 67;
+ public static D:number = 68;
+ public static E:number = 69;
+ public static F:number = 70;
+ public static G:number = 71;
+ public static H:number = 72;
+ public static I:number = 73;
+ public static J:number = 74;
+ public static K:number = 75;
+ public static L:number = 76;
+ public static M:number = 77;
+ public static N:number = 78;
+ public static O:number = 79;
+ public static P:number = 80;
+ public static Q:number = 81;
+ public static R:number = 82;
+ public static S:number = 83;
+ public static T:number = 84;
+ public static U:number = 85;
+ public static V:number = 86;
+ public static W:number = 87;
+ public static X:number = 88;
+ public static Y:number = 89;
+ public static Z:number = 90;
+ public static NUM_0:number = 48;
+ public static NUM_1:number = 49;
+ public static NUM_2:number = 50;
+ public static NUM_3:number = 51;
+ public static NUM_4:number = 52;
+ public static NUM_5:number = 53;
+ public static NUM_6:number = 54;
+ public static NUM_7:number = 55;
+ public static NUM_8:number = 56;
+ public static NUM_9:number = 57;
+ public static SUBSTRACT:number = 189;
+ public static ADD:number = 187;
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/LABColor.ts b/src/client/northstar/utils/LABColor.ts
new file mode 100644
index 000000000..72e46fb7f
--- /dev/null
+++ b/src/client/northstar/utils/LABColor.ts
@@ -0,0 +1,90 @@
+
+export class LABColor {
+ public L: number;
+ public A: number;
+ public B: number;
+
+ // constructor - takes three floats for lightness and color-opponent dimensions
+ constructor(l: number, a: number, b: number) {
+ this.L = l;
+ this.A = a;
+ this.B = b;
+ }
+
+ // static function for linear interpolation between two LABColors
+ public static Lerp(a: LABColor, b: LABColor, t: number): LABColor {
+ return new LABColor(LABColor.LerpNumber(a.L, b.L, t), LABColor.LerpNumber(a.A, b.A, t), LABColor.LerpNumber(a.B, b.B, t));
+ }
+
+ public static LerpNumber(a: number, b: number, percent: number): number {
+ return a + percent * (b - a);
+ }
+
+ static hexToRGB(hex: number, alpha: number): number[] {
+ var r = (hex & (0xff << 16)) >> 16;
+ var g = (hex & (0xff << 8)) >> 8;
+ var b = (hex & (0xff << 0)) >> 0;
+ return [r, g, b];
+ }
+ static RGBtoHex(red: number, green: number, blue: number): number {
+ return blue | (green << 8) | (red << 16);
+ }
+
+ public static RGBtoHexString(rgb: number): string {
+ let str = "#" + this.hex((rgb & (0xff << 16)) >> 16) + this.hex((rgb & (0xff << 8)) >> 8) + this.hex((rgb & (0xff << 0)) >> 0);
+ return str;
+ }
+
+ static hex(x: number): string {
+ var hexDigits = new Array
+ ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f");
+ return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16];
+ }
+
+ public static FromColor(c: number): LABColor {
+ var rgb = LABColor.hexToRGB(c, 0);
+ var r = LABColor.d3_rgb_xyz(rgb[0] * 255);
+ var g = LABColor.d3_rgb_xyz(rgb[1] * 255);
+ var b = LABColor.d3_rgb_xyz(rgb[2] * 255);
+
+ var x = LABColor.d3_xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / LABColor.d3_lab_X);
+ var y = LABColor.d3_xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / LABColor.d3_lab_Y);
+ var z = LABColor.d3_xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / LABColor.d3_lab_Z);
+ var lab = new LABColor(116 * y - 16, 500 * (x - y), 200 * (y - z));
+ return lab;
+ }
+
+ private static d3_lab_X: number = 0.950470;
+ private static d3_lab_Y: number = 1;
+ private static d3_lab_Z: number = 1.088830;
+
+ public static d3_lab_xyz(x: number): number {
+ return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
+ }
+
+ public static d3_xyz_rgb(r: number): number {
+ return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055));
+ }
+
+ public static d3_rgb_xyz(r: number): number {
+ return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
+ }
+
+ public static d3_xyz_lab(x: number): number {
+ return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
+ }
+
+ public static ToColor(lab: LABColor): number {
+ var y = (lab.L + 16) / 116;
+ var x = y + lab.A / 500;
+ var z = y - lab.B / 200;
+ x = LABColor.d3_lab_xyz(x) * LABColor.d3_lab_X;
+ y = LABColor.d3_lab_xyz(y) * LABColor.d3_lab_Y;
+ z = LABColor.d3_lab_xyz(z) * LABColor.d3_lab_Z;
+
+ return LABColor.RGBtoHex(
+ LABColor.d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z) / 255,
+ LABColor.d3_xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z) / 255,
+ LABColor.d3_xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z) / 255);
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts
new file mode 100644
index 000000000..4b44f40c3
--- /dev/null
+++ b/src/client/northstar/utils/MathUtil.ts
@@ -0,0 +1,249 @@
+
+
+export class PIXIPoint {
+ public get x() { return this.coords[0]; }
+ public get y() { return this.coords[1]; }
+ public set x(value: number) { this.coords[0] = value; }
+ public set y(value: number) { this.coords[1] = value; }
+ public coords: number[] = [0, 0];
+ constructor(x: number, y: number) {
+ this.coords[0] = x;
+ this.coords[1] = y;
+ }
+}
+
+export class PIXIRectangle {
+ public x: number;
+ public y: number;
+ public width: number;
+ public height: number;
+ public get left() { return this.x; }
+ public get right() { return this.x + this.width; }
+ public get top() { return this.y; }
+ public get bottom() { return this.top + this.height; }
+ public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); }
+ constructor(x: number, y: number, width: number, height: number) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+}
+
+export class MathUtil {
+
+ public static EPSILON: number = 0.001;
+
+ public static Sign(value: number): number {
+ return value >= 0 ? 1 : -1;
+ }
+
+ public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
+ if (inline) {
+ p1.x += p2.x;
+ p1.y += p2.y;
+ return p1;
+ }
+ else {
+ return new PIXIPoint(p1.x + p2.x, p1.y + p2.y);
+ }
+ }
+
+ public static Perp(p1: PIXIPoint): PIXIPoint {
+ return new PIXIPoint(-p1.y, p1.x);
+ }
+
+ public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint {
+ if (inline) {
+ p1.x /= by;
+ p1.y /= by;
+ return p1;
+ }
+ else {
+ return new PIXIPoint(p1.x / by, p1.y / by);
+ }
+ }
+
+ public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) {
+ if (inline) {
+ p1.x *= by;
+ p1.y *= by;
+ return p1;
+ }
+ else {
+ return new PIXIPoint(p1.x * by, p1.y * by);
+ }
+ }
+
+ public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint {
+ if (inline) {
+ p1.x -= p2.x;
+ p1.y -= p2.y;
+ return p1;
+ }
+ else {
+ return new PIXIPoint(p1.x - p2.x, p1.y - p2.y);
+ }
+ }
+
+ public static Area(rect: PIXIRectangle): number {
+ return rect.width * rect.height;
+ }
+
+ public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) {
+ // Return minimum distance between line segment vw and point p
+ var l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt
+ if (l2 === 0.0) return MathUtil.Dist(p, v); // v === w case
+ // Consider the line extending the segment, parameterized as v + t (w - v).
+ // We find projection of point p onto the line.
+ // It falls where t = [(p-v) . (w-v)] / |w-v|^2
+ // We clamp t from [0,1] to handle points outside the segment vw.
+ var dot = MathUtil.Dot(
+ MathUtil.SubtractPoint(p, v),
+ MathUtil.SubtractPoint(w, v)) / l2;
+ var t = Math.max(0, Math.min(1, dot));
+ // Projection falls on the segment
+ var projection = MathUtil.AddPoint(v,
+ MathUtil.MultiplyConstant(
+ MathUtil.SubtractPoint(w, v), t));
+ return MathUtil.Dist(p, projection);
+ }
+
+ public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined {
+ var a1 = pe1.y - ps1.y;
+ var b1 = ps1.x - pe1.x;
+
+ var a2 = pe2.y - ps2.y;
+ var b2 = ps2.x - pe2.x;
+
+ var delta = a1 * b2 - a2 * b1;
+ if (delta === 0) {
+ return undefined;
+ }
+ var c2 = a2 * ps2.x + b2 * ps2.y;
+ var c1 = a1 * ps1.x + b1 * ps1.y;
+ var invdelta = 1 / delta;
+ return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta);
+ }
+
+ public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean {
+ if (p.x < rect.left - this.EPSILON) {
+ return false;
+ }
+ if (p.x > rect.right + this.EPSILON) {
+ return false;
+ }
+ if (p.y < rect.top - this.EPSILON) {
+ return false;
+ }
+ if (p.y > rect.bottom + this.EPSILON) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> {
+ var r1 = new PIXIPoint(rect.left, rect.top);
+ var r2 = new PIXIPoint(rect.right, rect.top);
+ var r3 = new PIXIPoint(rect.right, rect.bottom);
+ var r4 = new PIXIPoint(rect.left, rect.bottom);
+ var ret = new Array<PIXIPoint>();
+ var dist = this.Dist(lineFrom, lineTo);
+ var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2);
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
+ ret.push(inter);
+ }
+ inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3);
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
+ ret.push(inter);
+ }
+ inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4);
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
+ ret.push(inter);
+ }
+ inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1);
+ if (inter && this.PointInPIXIRectangle(inter, rect) &&
+ this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) {
+ ret.push(inter);
+ }
+ return ret;
+ }
+
+ public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle {
+ const left = Math.max(rect1.x, rect2.x);
+ const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width);
+ const top = Math.max(rect1.y, rect2.y);
+ const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height);
+ return new PIXIRectangle(left, top, right - left, bottom - top);
+ }
+
+ public static Dist(p1: PIXIPoint, p2: PIXIPoint): number {
+ return Math.sqrt(MathUtil.DistSquared(p1, p2));
+ }
+
+ public static Dot(p1: PIXIPoint, p2: PIXIPoint): number {
+ return p1.x * p2.x + p1.y * p2.y;
+ }
+
+ public static Normalize(p1: PIXIPoint) {
+ var d = this.Length(p1);
+ return new PIXIPoint(p1.x / d, p1.y / d);
+ }
+
+ public static Length(p1: PIXIPoint): number {
+ return Math.sqrt(p1.x * p1.x + p1.y * p1.y);
+ }
+
+ public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number {
+ const a = p1.x - p2.x;
+ const b = p1.y - p2.y;
+ return (a * a + b * b);
+ }
+
+ public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean {
+ return !(r2.x > r1.x + r1.width ||
+ r2.x + r2.width < r1.x ||
+ r2.y > r1.y + r1.height ||
+ r2.y + r2.height < r1.y);
+ }
+
+ public static ArgMin(temp: number[]): number {
+ let index = 0;
+ let value = temp[0];
+ for (let i = 1; i < temp.length; i++) {
+ if (temp[i] < value) {
+ value = temp[i];
+ index = i;
+ }
+ }
+ return index;
+ }
+
+ public static ArgMax(temp: number[]): number {
+ let index = 0;
+ let value = temp[0];
+ for (let i = 1; i < temp.length; i++) {
+ if (temp[i] > value) {
+ value = temp[i];
+ index = i;
+ }
+ }
+ return index;
+ }
+
+ public static Combinations<T>(chars: T[]) {
+ let result = new Array<T>();
+ let f = (prefix: any, chars: any) => {
+ for (let i = 0; i < chars.length; i++) {
+ result.push(prefix.concat(chars[i]));
+ f(prefix.concat(chars[i]), chars.slice(i + 1));
+ }
+ };
+ f([], chars);
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/PartialClass.ts b/src/client/northstar/utils/PartialClass.ts
new file mode 100644
index 000000000..2f20de96f
--- /dev/null
+++ b/src/client/northstar/utils/PartialClass.ts
@@ -0,0 +1,7 @@
+
+export class PartialClass<T> {
+
+ constructor(data?: Partial<T>) {
+ Object.assign(this, data);
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts
new file mode 100644
index 000000000..a52890ed9
--- /dev/null
+++ b/src/client/northstar/utils/SizeConverter.ts
@@ -0,0 +1,101 @@
+import { PIXIPoint } from "./MathUtil";
+import { VisualBinRange } from "../model/binRanges/VisualBinRange";
+import { Bin, DoubleValueAggregateResult, AggregateKey } from "../model/idea/idea";
+import { ModelHelpers } from "../model/ModelHelpers";
+import { observable, action, computed } from "mobx";
+
+export class SizeConverter {
+ public DataMins: Array<number> = new Array<number>(2);
+ public DataMaxs: Array<number> = new Array<number>(2);
+ public DataRanges: Array<number> = new Array<number>(2);
+ public MaxLabelSizes: Array<PIXIPoint> = new Array<PIXIPoint>(2);
+ public RenderDimension: number = 300;
+
+ @observable _leftOffset: number = 40;
+ @observable _rightOffset: number = 20;
+ @observable _topOffset: number = 20;
+ @observable _bottomOffset: number = 45;
+ @observable _labelAngle: number = 0;
+ @observable _isSmall: boolean = false;
+ @observable public Initialized = 0;
+
+ @action public SetIsSmall(isSmall: boolean) { this._isSmall = isSmall; }
+ @action public SetLabelAngle(angle: number) { this._labelAngle = angle; }
+ @computed public get IsSmall() { return this._isSmall; }
+ @computed public get LabelAngle() { return this._labelAngle; }
+ @computed public get LeftOffset() { return this.IsSmall ? 5 : this._leftOffset; }
+ @computed public get RightOffset() { return this.IsSmall ? 5 : !this._labelAngle ? this._bottomOffset : Math.max(this._rightOffset, Math.cos(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)); }
+ @computed public get TopOffset() { return this.IsSmall ? 5 : this._topOffset; }
+ @computed public get BottomOffset() { return this.IsSmall ? 25 : !this._labelAngle ? this._bottomOffset : Math.max(this._bottomOffset, Math.sin(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)) + 18; }
+
+ public SetVisualBinRanges(visualBinRanges: Array<VisualBinRange>) {
+ this.Initialized++;
+ var xLabels = visualBinRanges[0].GetLabels();
+ var yLabels = visualBinRanges[1].GetLabels();
+ var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; });
+ var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length; });
+
+ var metricsX = { width: 75 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
+ //xLabelStrings[0]!.slice(0, 20)) // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS));
+ var metricsY = { width: 22 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number,
+ // yLabelStrings[0]!.slice(0, 20)); // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS));
+ this.MaxLabelSizes[0] = new PIXIPoint(metricsX.width, 12);// FontStyles.AxisLabel.fontSize as number);
+ this.MaxLabelSizes[1] = new PIXIPoint(metricsY.width, 12); // FontStyles.AxisLabel.fontSize as number);
+
+ this._leftOffset = Math.max(10, metricsY.width + 10 + 20);
+
+ this.DataMins[0] = xLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE);
+ this.DataMins[1] = yLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE);
+ this.DataMaxs[0] = xLabels.map(l => l.maxValue!).reduce((m, c) => Math.max(m, c), Number.MIN_VALUE);
+ this.DataMaxs[1] = yLabels.map(l => l.maxValue!).reduce((m, c) => Math.max(m, c), Number.MIN_VALUE);
+
+ this.DataRanges[0] = this.DataMaxs[0] - this.DataMins[0];
+ this.DataRanges[1] = this.DataMaxs[1] - this.DataMins[1];
+ }
+
+ public DataToScreenNormalizedRange(dataValue: number, normalization: number, axis: number, binBrushMaxAxis: number) {
+ var value = normalization !== 1 - axis || binBrushMaxAxis === 0 ? dataValue : (dataValue - 0) / (binBrushMaxAxis - 0) * this.DataRanges[axis];
+ var from = this.DataToScreenCoord(Math.min(0, value), axis);
+ var to = this.DataToScreenCoord(Math.max(0, value), axis);
+ return [from, value, to];
+ }
+
+ public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) {
+ var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult;
+ if (value && value.hasResult) {
+ return [this.DataToScreenCoord(value.result!, axis) - 5,
+ this.DataToScreenCoord(value.result!, axis) + 5];
+ }
+ return [undefined, undefined];
+ }
+
+ public DataToScreenXAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
+ var value = visualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![index]);
+ return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))];
+ }
+ public DataToScreenYAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) {
+ var value = visualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![index]);
+ return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))];
+ }
+
+ public DataToScreenX(x: number): number {
+ return ((x - this.DataMins[0]) / this.DataRanges[0]) * this.RenderDimension;
+ }
+ public DataToScreenY(y: number, flip: boolean = true) {
+ var retY = ((y - this.DataMins[1]) / this.DataRanges[1]) * this.RenderDimension;
+ return flip ? (this.RenderDimension) - retY : retY;
+ }
+ public DataToScreenCoord(v: number, axis: number) {
+ if (axis === 0) {
+ return this.DataToScreenX(v);
+ }
+ return this.DataToScreenY(v);
+ }
+ public DataToScreenRange(minVal: number, maxVal: number, axis: number) {
+ let xFrom = this.DataToScreenX(axis === 0 ? minVal : this.DataMins[0]);
+ let xTo = this.DataToScreenX(axis === 0 ? maxVal : this.DataMaxs[0]);
+ let yFrom = this.DataToScreenY(axis === 1 ? minVal : this.DataMins[1]);
+ let yTo = this.DataToScreenY(axis === 1 ? maxVal : this.DataMaxs[1]);
+ return { xFrom, yFrom, xTo, yTo };
+ }
+} \ No newline at end of file
diff --git a/src/client/northstar/utils/StyleContants.ts b/src/client/northstar/utils/StyleContants.ts
new file mode 100644
index 000000000..e9b6e0297
--- /dev/null
+++ b/src/client/northstar/utils/StyleContants.ts
@@ -0,0 +1,95 @@
+import { PIXIPoint } from "./MathUtil";
+
+export class StyleConstants {
+
+ static DEFAULT_FONT: string = "Roboto Condensed";
+
+ static MENU_SUBMENU_WIDTH: number = 85;
+ static MENU_SUBMENU_HEIGHT: number = 400;
+ static MENU_BOX_SIZE: PIXIPoint = new PIXIPoint(80, 35);
+ static MENU_BOX_PADDING: number = 10;
+
+ static OPERATOR_MENU_LARGE: number = 35;
+ static OPERATOR_MENU_SMALL: number = 25;
+ static BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd];
+ static GAP: number = 3;
+
+ static BACKGROUND_COLOR: number = 0xF3F3F3;
+ static TOOL_TIP_BACKGROUND_COLOR: number = 0xffffff;
+ static LIGHT_TEXT_COLOR: number = 0xffffff;
+ static LIGHT_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.LIGHT_TEXT_COLOR);
+ static DARK_TEXT_COLOR: number = 0x282828;
+ static HIGHLIGHT_TEXT_COLOR: number = 0xffcc00;
+ static FPS_TEXT_COLOR: number = StyleConstants.DARK_TEXT_COLOR;
+ static CORRELATION_LABEL_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.DARK_TEXT_COLOR);
+ static LOADING_SCREEN_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.DARK_TEXT_COLOR);
+ static ERROR_COLOR: number = 0x540E25;
+ static WARNING_COLOR: number = 0xE58F24;
+ static LOWER_THAN_NAIVE_COLOR: number = 0xee0000;
+ static HIGHLIGHT_COLOR: number = 0x82A8D9;
+ static HIGHLIGHT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.HIGHLIGHT_COLOR);
+ static OPERATOR_BACKGROUND_COLOR: number = 0x282828;
+ static LOADING_ANIMATION_COLOR: number = StyleConstants.OPERATOR_BACKGROUND_COLOR;
+ static MENU_COLOR: number = 0x282828;
+ static MENU_FONT_COLOR: number = StyleConstants.LIGHT_TEXT_COLOR;
+ static MENU_SELECTED_COLOR: number = StyleConstants.HIGHLIGHT_COLOR;
+ static MENU_SELECTED_FONT_COLOR: number = StyleConstants.LIGHT_TEXT_COLOR;
+ static BRUSH_COLOR: number = 0xff0000;
+ static DROP_ACCEPT_COLOR: number = StyleConstants.HIGHLIGHT_COLOR;
+ static SELECTED_COLOR: number = 0xffffff;
+ static SELECTED_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.SELECTED_COLOR);
+ static PROGRESS_BACKGROUND_COLOR: number = 0x595959;
+ static GRID_LINES_COLOR: number = 0x3D3D3D;
+ static GRID_LINES_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.GRID_LINES_COLOR);
+
+ static MAX_CHAR_FOR_HISTOGRAM_LABELS: number = 20;
+
+ static OVERLAP_COLOR: number = 0x0000ff;//0x540E25;
+ static BRUSH_COLORS: Array<number> = new Array<number>(
+ 0xFFDA7E, 0xFE8F65, 0xDA5655, 0x8F2240
+ );
+
+ static MIN_VALUE_COLOR: number = 0x373d43; //32343d, 373d43, 3b4648
+ static MARGIN_BARS_COLOR: number = 0xffffff;
+ static MARGIN_BARS_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.MARGIN_BARS_COLOR);
+
+ static HISTOGRAM_WIDTH: number = 200;
+ static HISTOGRAM_HEIGHT: number = 150;
+ static PREDICTOR_WIDTH: number = 150;
+ static PREDICTOR_HEIGHT: number = 100;
+ static RAWDATA_WIDTH: number = 150;
+ static RAWDATA_HEIGHT: number = 100;
+ static FREQUENT_ITEM_WIDTH: number = 180;
+ static FREQUENT_ITEM_HEIGHT: number = 100;
+ static CORRELATION_WIDTH: number = 555;
+ static CORRELATION_HEIGHT: number = 390;
+ static PROBLEM_FINDER_WIDTH: number = 450;
+ static PROBLEM_FINDER_HEIGHT: number = 150;
+ static PIPELINE_OPERATOR_WIDTH: number = 300;
+ static PIPELINE_OPERATOR_HEIGHT: number = 120;
+ static SLICE_WIDTH: number = 150;
+ static SLICE_HEIGHT: number = 45;
+ static BORDER_MENU_ITEM_WIDTH: number = 50;
+ static BORDER_MENU_ITEM_HEIGHT: number = 30;
+
+
+ static SLICE_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.OPERATOR_BACKGROUND_COLOR);
+ static SLICE_EMPTY_COLOR: number = StyleConstants.OPERATOR_BACKGROUND_COLOR;
+ static SLICE_OCCUPIED_COLOR: number = 0xffffff;
+ static SLICE_OCCUPIED_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.OPERATOR_BACKGROUND_COLOR);
+ static SLICE_HOVER_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.HIGHLIGHT_COLOR);
+ static SLICE_HOVER_COLOR: number = 0xffffff;
+
+ static HexToHexString(hex: number): string {
+ if (hex === undefined) {
+ return "#000000";
+ }
+ var s = hex.toString(16);
+ while (s.length < 6) {
+ s = "0" + s;
+ }
+ return "#" + s;
+ }
+
+
+}
diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts
new file mode 100644
index 000000000..d071dec62
--- /dev/null
+++ b/src/client/northstar/utils/Utils.ts
@@ -0,0 +1,75 @@
+import { IBaseBrushable } from '../core/brusher/IBaseBrushable';
+import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer';
+import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider';
+import { AggregateFunction } from '../model/idea/idea';
+
+export class Utils {
+
+ public static EqualityHelper(a: Object, b: Object): boolean {
+ if (a === b) return true;
+ if (a === undefined && b !== undefined) return false;
+ if (a === null && b !== null) return false;
+ if (b === undefined && a !== undefined) return false;
+ if (b === null && a !== null) return false;
+ if ((<any>a).constructor.name !== (<any>b).constructor.name) return false;
+ return true;
+ }
+
+ public static LowercaseFirstLetter(str: string) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+ }
+
+ //
+ // this Type Guard tests if dropTarget is an IDropTarget. If it is, it coerces the compiler
+ // to treat the dropTarget parameter as an IDropTarget *ouside* this function scope (ie, in
+ // the scope of where this function is called from).
+ //
+
+ public static isBaseBrushable<T>(obj: Object): obj is IBaseBrushable<T> {
+ let typed = <IBaseBrushable<T>>obj;
+ return typed !== null && typed.BrusherModels !== undefined;
+ }
+
+ public static isBaseFilterProvider(obj: Object): obj is IBaseFilterProvider {
+ let typed = <IBaseFilterProvider>obj;
+ return typed !== null && typed.FilterModels !== undefined;
+ }
+
+ public static isBaseFilterConsumer(obj: Object): obj is IBaseFilterConsumer {
+ let typed = <IBaseFilterConsumer>obj;
+ return typed !== null && typed.FilterOperand !== undefined;
+ }
+
+ public static EncodeQueryData(data: any): string {
+ const ret = [];
+ for (let d in data) {
+ ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
+ }
+ return ret.join("&");
+ }
+
+ public static ToVegaAggregationString(agg: AggregateFunction): string {
+ if (agg === AggregateFunction.Avg) {
+ return "average";
+ }
+ else if (agg === AggregateFunction.Count) {
+ return "count";
+ }
+ else {
+ return "";
+ }
+ }
+
+ public static GetQueryVariable(variable: string) {
+ let query = window.location.search.substring(1);
+ let vars = query.split("&");
+ for (const variable of vars) {
+ let pair = variable.split("=");
+ if (decodeURIComponent(pair[0]) === variable) {
+ return decodeURIComponent(pair[1]);
+ }
+ }
+ return undefined;
+ }
+}
+
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 5b99b4ef8..d06af6dd5 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,8 +1,14 @@
-import React = require('react')
-import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
-import { Document } from "../../fields/Document"
+import { computed, observable } from 'mobx';
import { DocumentView } from '../views/nodes/DocumentView';
+import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
+import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
+import { listSpec } from '../../new_fields/Schema';
+import { undoBatch } from './UndoManager';
+import { CollectionDockingView } from '../views/collections/CollectionDockingView';
+import { Id } from '../../new_fields/RefField';
+import { CollectionView } from '../views/collections/CollectionView';
+import { CollectionPDFView } from '../views/collections/CollectionPDFView';
+import { CollectionVideoView } from '../views/collections/CollectionVideoView';
export class DocumentManager {
@@ -24,28 +30,114 @@ export class DocumentManager {
// this.DocumentViews = new Array<DocumentView>();
}
- public getDocumentView(toFind: Document): DocumentView | null {
+ public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | null {
- let toReturn: DocumentView | null;
- toReturn = null;
+ let toReturn: DocumentView | null = null;
+ let passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
+
+ for (let i = 0; i < passes.length; i++) {
+ DocumentManager.Instance.DocumentViews.map(view => {
+ if (view.props.Document[Id] === id && (!passes[i] || view.props.ContainingCollectionView === preferredCollection)) {
+ toReturn = view;
+ return;
+ }
+ });
+ if (!toReturn) {
+ DocumentManager.Instance.DocumentViews.map(view => {
+ let doc = view.props.Document.proto;
+ if (doc && doc[Id] === id && (!passes[i] || view.props.ContainingCollectionView === preferredCollection)) {
+ toReturn = view;
+ }
+ });
+ }
+ }
+
+ return toReturn;
+ }
+
+ public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | null {
+ return this.getDocumentViewById(toFind[Id], preferredCollection);
+ }
+
+ public getDocumentViews(toFind: Doc): DocumentView[] {
+
+ let toReturn: DocumentView[] = [];
//gets document view that is in a freeform canvas collection
DocumentManager.Instance.DocumentViews.map(view => {
let doc = view.props.Document;
// if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
- // if (Object.is(doc, toFind)) {
- // toReturn = view;
- // return;
- // }
- // }
-
- if (Object.is(doc, toFind)) {
- toReturn = view;
- return;
+
+ if (doc === toFind) {
+ toReturn.push(view);
+ } else {
+ let docSrc = FieldValue(doc.proto);
+ if (docSrc && Object.is(docSrc, toFind)) {
+ toReturn.push(view);
+ }
}
+ });
- })
+ return toReturn;
+ }
- return (toReturn);
+ @computed
+ public get LinkedDocumentViews() {
+ return DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
+ let linksList = DocListCast(dv.props.Document.linkedToDocs);
+ if (linksList && linksList.length) {
+ pairs.push(...linksList.reduce((pairs, link) => {
+ if (link) {
+ let linkToDoc = FieldValue(Cast(link.linkedTo, Doc));
+ if (linkToDoc) {
+ DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 =>
+ pairs.push({ a: dv, b: docView1, l: link }));
+ }
+ }
+ return pairs;
+ }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
+ }
+ linksList = DocListCast(dv.props.Document.linkedFromDocs);
+ if (linksList && linksList.length) {
+ pairs.push(...linksList.reduce((pairs, link) => {
+ if (link) {
+ let linkFromDoc = FieldValue(Cast(link.linkedFrom, Doc));
+ if (linkFromDoc) {
+ DocumentManager.Instance.getDocumentViews(linkFromDoc).map(docView1 =>
+ pairs.push({ a: dv, b: docView1, l: link }));
+ }
+ }
+ return pairs;
+ }, pairs));
+ }
+ return pairs;
+ }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
+ }
+
+ @undoBatch
+ public jumpToDocument = async (docDelegate: Doc, makeCopy: boolean = true): Promise<void> => {
+ let doc = docDelegate.proto ? docDelegate.proto : docDelegate;
+ const page = NumCast(doc.page, undefined);
+ const contextDoc = await Cast(doc.annotationOn, Doc);
+ if (contextDoc) {
+ const curPage = NumCast(contextDoc.curPage, page);
+ if (page !== curPage) contextDoc.curPage = page;
+ }
+ let docView = DocumentManager.Instance.getDocumentView(doc);
+ if (docView) {
+ docView.props.focus(docView.props.Document);
+ } else {
+ if (!contextDoc) {
+ CollectionDockingView.Instance.AddRightSplit(docDelegate ? (makeCopy ? Doc.MakeCopy(docDelegate) : docDelegate) : Doc.MakeDelegate(doc));
+ } else {
+ let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
+ if (contextView) {
+ contextDoc.panTransformType = "Ease";
+ contextView.props.focus(contextDoc);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(contextDoc);
+ }
+ }
+ }
}
} \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4a61220a5..7f75a95f0 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,40 +1,70 @@
-import { DocumentDecorations } from "../views/DocumentDecorations";
+import { action, runInAction } from "mobx";
+import { Doc, DocListCastAsync } from "../../new_fields/Doc";
+import { Cast } from "../../new_fields/Types";
+import { emptyFunction } from "../../Utils";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { Document } from "../../fields/Document"
-import { action } from "mobx";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { ImageField } from "../../fields/ImageField";
-import { KeyStore } from "../../fields/KeyStore";
-
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document) {
- let onRowMove = action((e: PointerEvent): void => {
+import * as globalCssVariables from "../views/globalCssVariables.scss";
+
+export type dropActionType = "alias" | "copy" | undefined;
+export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc>, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) {
+ let onRowMove = async (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
- DragManager.StartDrag(_reference.current!, { document: docFunc() });
- });
- let onRowUp = action((e: PointerEvent): void => {
+ var dragData = new DragManager.DocumentDragData([await docFunc()]);
+ dragData.dropAction = dropAction;
+ dragData.moveDocument = moveFunc;
+ DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
+ };
+ let onRowUp = (): void => {
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
- });
- let onItemDown = (e: React.PointerEvent) => {
+ };
+ let onItemDown = async (e: React.PointerEvent) => {
// if (this.props.isSelected() || this.props.isTopMost) {
- if (e.button == 0) {
+ if (e.button === 0) {
e.stopPropagation();
if (e.shiftKey) {
- CollectionDockingView.Instance.StartOtherDrag(docFunc(), e);
+ CollectionDockingView.Instance.StartOtherDrag([await docFunc()], e);
} else {
document.addEventListener("pointermove", onRowMove);
- document.addEventListener('pointerup', onRowUp);
+ document.addEventListener("pointerup", onRowUp);
}
}
//}
- }
+ };
return onItemDown;
}
+export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
+ let srcTarg = sourceDoc.proto;
+ let draggedDocs: Doc[] = [];
+ let draggedFromDocs: Doc[] = [];
+ if (srcTarg) {
+ let linkToDocs = await DocListCastAsync(srcTarg.linkedToDocs);
+ let linkFromDocs = await DocListCastAsync(srcTarg.linkedFromDocs);
+ if (linkToDocs) draggedDocs = linkToDocs.map(linkDoc => Cast(linkDoc.linkedTo, Doc) as Doc);
+ if (linkFromDocs) draggedFromDocs = linkFromDocs.map(linkDoc => Cast(linkDoc.linkedFrom, Doc) as Doc);
+ }
+ draggedDocs.push(...draggedFromDocs);
+ if (draggedDocs.length) {
+ let moddrag: Doc[] = [];
+ for (const draggedDoc of draggedDocs) {
+ let doc = await Cast(draggedDoc.annotationOn, Doc);
+ if (doc) moddrag.push(doc);
+ }
+ let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
+ DragManager.StartDocumentDrag([dragEle], dragData, x, y, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
+}
+
export namespace DragManager {
export function Root() {
const root = document.getElementById("root");
@@ -47,7 +77,9 @@ export namespace DragManager {
let dragDiv: HTMLDivElement;
export enum DragButtons {
- Left = 1, Right = 2, Both = Left | Right
+ Left = 1,
+ Right = 2,
+ Both = Left | Right
}
interface DragOptions {
@@ -60,8 +92,7 @@ export namespace DragManager {
(): void;
}
- export class DragCompleteEvent {
- }
+ export class DragCompleteEvent { }
export interface DragHandlers {
dragComplete: (e: DragCompleteEvent) => void;
@@ -70,21 +101,29 @@ export namespace DragManager {
export interface DropOptions {
handlers: DropHandlers;
}
-
export class DropEvent {
- constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { }
+ constructor(
+ readonly x: number,
+ readonly y: number,
+ readonly data: { [id: string]: any },
+ readonly mods: string
+ ) { }
}
export interface DropHandlers {
drop: (e: Event, de: DropEvent) => void;
}
-
- export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
+ export function MakeDropTarget(
+ element: HTMLElement,
+ options: DropOptions
+ ): DragDropDisposer {
if ("canDrop" in element.dataset) {
- throw new Error("Element is already droppable, can't make it droppable again");
+ throw new Error(
+ "Element is already droppable, can't make it droppable again"
+ );
}
- element.dataset["canDrop"] = "true";
+ element.dataset.canDrop = "true";
const handler = (e: Event) => {
const ce = e as CustomEvent<DropEvent>;
options.handlers.drop(e, ce.detail);
@@ -92,54 +131,120 @@ export namespace DragManager {
element.addEventListener("dashOnDrop", handler);
return () => {
element.removeEventListener("dashOnDrop", handler);
- delete element.dataset["canDrop"]
+ delete element.dataset.canDrop;
};
}
- export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions) {
+ export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ export class DocumentDragData {
+ constructor(dragDoc: Doc[]) {
+ this.draggedDocuments = dragDoc;
+ this.droppedDocuments = dragDoc;
+ this.xOffset = 0;
+ this.yOffset = 0;
+ }
+ draggedDocuments: Doc[];
+ droppedDocuments: Doc[];
+ xOffset: number;
+ yOffset: number;
+ dropAction: dropActionType;
+ userDropAction: dropActionType;
+ moveDocument?: MoveFunction;
+ [id: string]: any;
+ }
+
+ export let StartDragFunctions: (() => void)[] = [];
+
+ export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+ runInAction(() => StartDragFunctions.map(func => func()));
+ StartDrag(eles, dragData, downX, downY, options,
+ (dropData: { [id: string]: any }) =>
+ (dropData.droppedDocuments = dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ?
+ dragData.draggedDocuments.map(d => Doc.MakeAlias(d)) :
+ dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ?
+ dragData.draggedDocuments.map(d => Doc.MakeCopy(d, true)) :
+ dragData.draggedDocuments));
+ }
+
+ export class LinkDragData {
+ constructor(linkSourceDoc: Doc, blacklist: Doc[] = []) {
+ this.linkSourceDocument = linkSourceDoc;
+ this.blacklist = blacklist;
+ }
+ droppedDocuments: Doc[] = [];
+ linkSourceDocument: Doc;
+ blacklist: Doc[];
+ dontClearTextBox?: boolean;
+ [id: string]: any;
+ }
+
+ export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) {
+ StartDrag([ele], dragData, downX, downY, options);
+ }
+
+ export let AbortDrag: () => void = emptyFunction;
+
+ function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
if (!dragDiv) {
dragDiv = document.createElement("div");
- dragDiv.className = "dragManager-dragDiv"
+ dragDiv.className = "dragManager-dragDiv";
+ dragDiv.style.pointerEvents = "none";
DragManager.Root().appendChild(dragDiv);
}
- const w = ele.offsetWidth, h = ele.offsetHeight;
- const rect = ele.getBoundingClientRect();
- const scaleX = rect.width / w, scaleY = rect.height / h;
- let x = rect.left, y = rect.top;
- // const offsetX = e.x - rect.left, offsetY = e.y - rect.top;
-
- let dragElement = ele.cloneNode(true) as HTMLElement;
- dragElement.style.opacity = "0.7";
- dragElement.style.position = "absolute";
- dragElement.style.bottom = "";
- dragElement.style.left = "";
- dragElement.style.transformOrigin = "0 0";
- dragElement.style.zIndex = "1000";
- dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
- dragElement.style.width = `${rect.width / scaleX}px`;
- dragElement.style.height = `${rect.height / scaleY}px`;
-
- // bcz: PDFs don't show up if you clone them because they contain a canvas.
- // however, PDF's have a thumbnail field that contains an image of their canvas.
- // So we replace the pdf's canvas with the image thumbnail
- const docView: DocumentView = dragData["documentView"];
- const doc: Document = docView ? docView.props.Document : dragData["document"];
-
- if (doc) {
- var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
- let thumbnail = doc.GetT(KeyStore.Thumbnail, ImageField);
- if (pdfBox && pdfBox.childElementCount && thumbnail) {
- let img = new Image();
- img!.src = thumbnail.toString();
- img!.style.position = "absolute";
- img!.style.width = `${rect.width / scaleX}px`;
- img!.style.height = `${rect.height / scaleY}px`;
- pdfBox.replaceChild(img!, pdfBox.children[0])
- }
- }
+ let scaleXs: number[] = [];
+ let scaleYs: number[] = [];
+ let xs: number[] = [];
+ let ys: number[] = [];
+
+ const docs: Doc[] =
+ dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
+ let dragElements = eles.map(ele => {
+ const w = ele.offsetWidth,
+ h = ele.offsetHeight;
+ const rect = ele.getBoundingClientRect();
+ const scaleX = rect.width / w,
+ scaleY = rect.height / h;
+ let x = rect.left,
+ y = rect.top;
+ xs.push(x);
+ ys.push(y);
+ scaleXs.push(scaleX);
+ scaleYs.push(scaleY);
+ let dragElement = ele.cloneNode(true) as HTMLElement;
+ dragElement.style.opacity = "0.7";
+ dragElement.style.position = "absolute";
+ dragElement.style.margin = "0";
+ dragElement.style.top = "0";
+ dragElement.style.bottom = "";
+ dragElement.style.left = "0";
+ dragElement.style.color = "black";
+ dragElement.style.transformOrigin = "0 0";
+ dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
+ dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ dragElement.style.width = `${rect.width / scaleX}px`;
+ dragElement.style.height = `${rect.height / scaleY}px`;
+
+ // bcz: if PDFs are rendered with svg's, then this code isn't needed
+ // bcz: PDFs don't show up if you clone them when rendered using a canvas.
+ // however, PDF's have a thumbnail field that contains an image of their canvas.
+ // So we replace the pdf's canvas with the image thumbnail
+ // if (docs.length) {
+ // var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
+ // let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField);
+ // if (pdfBox && pdfBox.childElementCount && thumbnail) {
+ // let img = new Image();
+ // img.src = thumbnail.toString();
+ // img.style.position = "absolute";
+ // img.style.width = `${rect.width / scaleX}px`;
+ // img.style.height = `${rect.height / scaleY}px`;
+ // pdfBox.replaceChild(img, pdfBox.children[0])
+ // }
+ // }
- dragDiv.appendChild(dragElement);
+ dragDiv.appendChild(dragElement);
+ return dragElement;
+ });
let hideSource = false;
if (options) {
@@ -149,59 +254,92 @@ export namespace DragManager {
hideSource = options.hideSource();
}
}
- const wasHidden = ele.hidden;
- if (hideSource) {
- ele.hidden = true;
- }
+ eles.map(ele => (ele.hidden = hideSource));
+
+ let lastX = downX;
+ let lastY = downY;
const moveHandler = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- x += e.movementX;
- y += e.movementY;
+ if (dragData instanceof DocumentDragData) {
+ dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
+ }
if (e.shiftKey) {
- abortDrag();
- CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
+ AbortDrag();
+ CollectionDockingView.Instance.StartOtherDrag(docs, {
+ pageX: e.pageX,
+ pageY: e.pageY,
+ preventDefault: emptyFunction,
+ button: 0
+ });
}
- dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ //TODO: Why can't we use e.movementX and e.movementY?
+ let moveX = e.pageX - lastX;
+ let moveY = e.pageY - lastY;
+ lastX = e.pageX;
+ lastY = e.pageY;
+ dragElements.map((dragElement, i) => (dragElement.style.transform =
+ `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px)
+ scale(${scaleXs[i]}, ${scaleYs[i]})`)
+ );
};
- const abortDrag = () => {
+ let hideDragElements = () => {
+ dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
+ eles.map(ele => (ele.hidden = false));
+ };
+ let endDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
- dragDiv.removeChild(dragElement);
- if (hideSource && !wasHidden) {
- ele.hidden = false;
+ if (options) {
+ options.handlers.dragComplete({});
}
- }
+ };
+
+ AbortDrag = () => {
+ hideDragElements();
+ endDrag();
+ };
const upHandler = (e: PointerEvent) => {
- abortDrag();
- FinishDrag(ele, e, dragData, options);
+ hideDragElements();
+ dispatchDrag(eles, e, dragData, options, finishDrag);
+ endDrag();
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
- function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions) {
- let parent = dragEle.parentElement;
- if (parent)
- parent.removeChild(dragEle);
+ function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
+ let removed = dragEles.map(dragEle => {
+ // let parent = dragEle.parentElement;
+ // if (parent) parent.removeChild(dragEle);
+ let ret = [dragEle, dragEle.style.width, dragEle.style.height];
+ dragEle.style.width = "0";
+ dragEle.style.height = "0";
+ return ret;
+ });
const target = document.elementFromPoint(e.x, e.y);
- if (parent)
- parent.appendChild(dragEle);
- if (!target) {
- return;
- }
- target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
- bubbles: true,
- detail: {
- x: e.x,
- y: e.y,
- data: dragData
- }
- }));
- if (options) {
- options.handlers.dragComplete({});
+ removed.map(r => {
+ let dragEle = r[0] as HTMLElement;
+ dragEle.style.width = r[1] as string;
+ dragEle.style.height = r[2] as string;
+ // let parent = r[1];
+ // if (parent && dragEle) parent.appendChild(dragEle);
+ });
+ if (target) {
+ if (finishDrag) finishDrag(dragData);
+
+ target.dispatchEvent(
+ new CustomEvent<DropEvent>("dashOnDrop", {
+ bubbles: true,
+ detail: {
+ x: e.x,
+ y: e.y,
+ data: dragData,
+ mods: e.altKey ? "AltKey" : ""
+ }
+ })
+ );
}
- DocumentDecorations.Instance.Hidden = false;
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
new file mode 100644
index 000000000..92d2b2b44
--- /dev/null
+++ b/src/client/util/History.ts
@@ -0,0 +1,122 @@
+import { Doc, Opt, Field } from "../../new_fields/Doc";
+import { DocServer } from "../DocServer";
+import { Main } from "../views/Main";
+import { RouteStore } from "../../server/RouteStore";
+
+export namespace HistoryUtil {
+ export interface DocInitializerList {
+ [key: string]: string | number;
+ }
+
+ export interface DocUrl {
+ type: "doc";
+ docId: string;
+ initializers: {
+ [docId: string]: DocInitializerList;
+ };
+ }
+
+ export type ParsedUrl = DocUrl;
+
+ // const handlers: ((state: ParsedUrl | null) => void)[] = [];
+ function onHistory(e: PopStateEvent) {
+ if (window.location.pathname !== RouteStore.home) {
+ const url = e.state as ParsedUrl || parseUrl(window.location.pathname);
+ if (url) {
+ switch (url.type) {
+ case "doc":
+ onDocUrl(url);
+ break;
+ }
+ }
+ }
+ // for (const handler of handlers) {
+ // handler(e.state);
+ // }
+ }
+
+ export function pushState(state: ParsedUrl) {
+ history.pushState(state, "", createUrl(state));
+ }
+
+ export function replaceState(state: ParsedUrl) {
+ history.replaceState(state, "", createUrl(state));
+ }
+
+ function copyState(state: ParsedUrl): ParsedUrl {
+ return JSON.parse(JSON.stringify(state));
+ }
+
+ export function getState(): ParsedUrl {
+ return copyState(history.state);
+ }
+
+ // export function addHandler(handler: (state: ParsedUrl | null) => void) {
+ // handlers.push(handler);
+ // }
+
+ // export function removeHandler(handler: (state: ParsedUrl | null) => void) {
+ // const index = handlers.indexOf(handler);
+ // if (index !== -1) {
+ // handlers.splice(index, 1);
+ // }
+ // }
+
+ export function parseUrl(pathname: string): ParsedUrl | undefined {
+ let pathnameSplit = pathname.split("/");
+ if (pathnameSplit.length !== 2) {
+ return undefined;
+ }
+ const type = pathnameSplit[0];
+ const data = pathnameSplit[1];
+
+ if (type === "doc") {
+ const s = data.split("?");
+ if (s.length < 1 || s.length > 2) {
+ return undefined;
+ }
+ const docId = s[0];
+ const initializers = s.length === 2 ? JSON.parse(decodeURIComponent(s[1])) : {};
+ return {
+ type: "doc",
+ docId,
+ initializers
+ };
+ }
+
+ return undefined;
+ }
+
+ export function createUrl(params: ParsedUrl): string {
+ let baseUrl = DocServer.prepend(`/${params.type}`);
+ switch (params.type) {
+ case "doc":
+ const initializers = encodeURIComponent(JSON.stringify(params.initializers));
+ const id = params.docId;
+ let url = baseUrl + `/${id}`;
+ if (Object.keys(params.initializers).length) {
+ url += `?${initializers}`;
+ }
+ return url;
+ }
+ return "";
+ }
+
+ export async function initDoc(id: string, initializer: DocInitializerList) {
+ const doc = await DocServer.GetRefField(id);
+ if (!(doc instanceof Doc)) {
+ return;
+ }
+ Doc.assign(doc, initializer);
+ }
+
+ async function onDocUrl(url: DocUrl) {
+ const field = await DocServer.GetRefField(url.docId);
+ await Promise.all(Object.keys(url.initializers).map(id => initDoc(id, url.initializers[id])));
+ if (field instanceof Doc) {
+ Main.Instance.openWorkspace(field, true);
+ }
+ }
+
+ window.onpopstate = onHistory;
+}
diff --git a/src/client/util/ProsemirrorKeymap.ts b/src/client/util/ProsemirrorKeymap.ts
new file mode 100644
index 000000000..00d086b97
--- /dev/null
+++ b/src/client/util/ProsemirrorKeymap.ts
@@ -0,0 +1,100 @@
+import { Schema } from "prosemirror-model";
+import {
+ wrapIn, setBlockType, chainCommands, toggleMark, exitCode,
+ joinUp, joinDown, lift, selectParentNode
+} from "prosemirror-commands";
+import { wrapInList, splitListItem, liftListItem, sinkListItem } from "prosemirror-schema-list";
+import { undo, redo } from "prosemirror-history";
+import { undoInputRule } from "prosemirror-inputrules";
+import { Transaction, EditorState } from "prosemirror-state";
+
+const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
+
+export type KeyMap = { [key: string]: any };
+
+export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: KeyMap): KeyMap {
+ let keys: { [key: string]: any } = {}, type;
+
+ function bind(key: string, cmd: any) {
+ if (mapKeys) {
+ let mapped = mapKeys[key];
+ if (mapped === false) return;
+ if (mapped) key = mapped;
+ }
+ keys[key] = cmd;
+ }
+
+ bind("Mod-z", undo);
+ bind("Shift-Mod-z", redo);
+ bind("Backspace", undoInputRule);
+
+ if (!mac) {
+ bind("Mod-y", redo);
+ }
+
+ bind("Alt-ArrowUp", joinUp);
+ bind("Alt-ArrowDown", joinDown);
+ bind("Mod-BracketLeft", lift);
+ bind("Escape", selectParentNode);
+
+ if (type = schema.marks.strong) {
+ bind("Mod-b", toggleMark(type));
+ bind("Mod-B", toggleMark(type));
+ }
+ if (type = schema.marks.em) {
+ bind("Mod-i", toggleMark(type));
+ bind("Mod-I", toggleMark(type));
+ }
+ if (type = schema.marks.code) {
+ bind("Mod-`", toggleMark(type));
+ }
+
+ if (type = schema.nodes.bullet_list) {
+ bind("Ctrl-b", wrapInList(type));
+ }
+ if (type = schema.nodes.ordered_list) {
+ bind("Ctrl-n", wrapInList(type));
+ }
+ if (type = schema.nodes.blockquote) {
+ bind("Ctrl->", wrapIn(type));
+ }
+ if (type = schema.nodes.hard_break) {
+ let br = type, cmd = chainCommands(exitCode, (state, dispatch) => {
+ if (dispatch) {
+ dispatch(state.tr.replaceSelectionWith(br.create()).scrollIntoView());
+ return true;
+ }
+ return false;
+ });
+ bind("Mod-Enter", cmd);
+ bind("Shift-Enter", cmd);
+ if (mac) {
+ bind("Ctrl-Enter", cmd);
+ }
+ }
+ if (type = schema.nodes.list_item) {
+ bind("Enter", splitListItem(type));
+ bind("Shift-Tab", liftListItem(type));
+ bind("Tab", sinkListItem(type));
+ }
+ if (type = schema.nodes.paragraph) {
+ bind("Shift-Ctrl-0", setBlockType(type));
+ }
+ if (type = schema.nodes.code_block) {
+ bind("Shift-Ctrl-\\", setBlockType(type));
+ }
+ if (type = schema.nodes.heading) {
+ for (let i = 1; i <= 6; i++) {
+ bind("Shift-Ctrl-" + i, setBlockType(type, { level: i }));
+ }
+ }
+ if (type = schema.nodes.horizontal_rule) {
+ let hr = type;
+ bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+ return true;
+ });
+ }
+
+ return keys;
+} \ No newline at end of file
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
new file mode 100644
index 000000000..3b8396510
--- /dev/null
+++ b/src/client/util/RichTextRules.ts
@@ -0,0 +1,43 @@
+import {
+ inputRules,
+ wrappingInputRule,
+ textblockTypeInputRule,
+ smartQuotes,
+ emDash,
+ ellipsis
+} from "prosemirror-inputrules";
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
+
+import { schema } from "./RichTextSchema";
+
+export const inpRules = {
+ rules: [
+ ...smartQuotes,
+ ellipsis,
+ emDash,
+
+ // > blockquote
+ wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote),
+
+ // 1. ordered list
+ wrappingInputRule(
+ /^(\d+)\.\s$/,
+ schema.nodes.ordered_list,
+ match => ({ order: +match[1] }),
+ (match, node) => node.childCount + node.attrs.order === +match[1]
+ ),
+
+ // * bullet list
+ wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
+
+ // ``` code block
+ textblockTypeInputRule(/^```$/, schema.nodes.code_block),
+
+ // # heading
+ textblockTypeInputRule(
+ new RegExp("^(#{1,6})\\s$"),
+ schema.nodes.heading,
+ match => ({ level: match[1].length })
+ )
+ ]
+};
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index abf448c9f..3e3e98206 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,129 +1,147 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray } from "prosemirror-model"
-import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands'
-import { redo, undo } from 'prosemirror-history'
-import { orderedList, bulletList, listItem } from 'prosemirror-schema-list'
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
+import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands';
+import { redo, undo } from 'prosemirror-history';
+import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list';
+import { EditorState, Transaction, NodeSelection, } from "prosemirror-state";
+import { EditorView, } from "prosemirror-view";
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
- preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]
+ preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
// :: Object
// [Specs](#model.NodeSpec) for the nodes defined in this schema.
export const nodes: { [index: string]: NodeSpec } = {
- // :: NodeSpec The top level document node.
- doc: {
- content: "block+"
- },
-
- // :: NodeSpec A plain paragraph textblock. Represented in the DOM
- // as a `<p>` element.
- paragraph: {
- content: "inline*",
- group: "block",
- parseDOM: [{ tag: "p" }],
- toDOM() { return pDOM }
- },
-
- // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
- blockquote: {
- content: "block+",
- group: "block",
- defining: true,
- parseDOM: [{ tag: "blockquote" }],
- toDOM() { return blockquoteDOM }
- },
-
- // :: NodeSpec A horizontal rule (`<hr>`).
- horizontal_rule: {
- group: "block",
- parseDOM: [{ tag: "hr" }],
- toDOM() { return hrDOM }
- },
-
- // :: NodeSpec A heading textblock, with a `level` attribute that
- // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
- // `<h6>` elements.
- heading: {
- attrs: { level: { default: 1 } },
- content: "inline*",
- group: "block",
- defining: true,
- parseDOM: [{ tag: "h1", attrs: { level: 1 } },
- { tag: "h2", attrs: { level: 2 } },
- { tag: "h3", attrs: { level: 3 } },
- { tag: "h4", attrs: { level: 4 } },
- { tag: "h5", attrs: { level: 5 } },
- { tag: "h6", attrs: { level: 6 } }],
- toDOM(node: any) { return ["h" + node.attrs.level, 0] }
- },
-
- // :: NodeSpec A code listing. Disallows marks or non-text inline
- // nodes by default. Represented as a `<pre>` element with a
- // `<code>` element inside of it.
- code_block: {
- content: "text*",
- marks: "",
- group: "block",
- code: true,
- defining: true,
- parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
- toDOM() { return preDOM }
- },
-
- // :: NodeSpec The text node.
- text: {
- group: "inline"
- },
-
- // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
- // `alt`, and `href` attributes. The latter two default to the empty
- // string.
- image: {
- inline: true,
- attrs: {
- src: {},
- alt: { default: null },
- title: { default: null }
- },
- group: "inline",
- draggable: true,
- parseDOM: [{
- tag: "img[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt")
+ // :: NodeSpec The top level document node.
+ doc: {
+ content: "block+"
+ },
+
+ // :: NodeSpec A plain paragraph textblock. Represented in the DOM
+ // as a `<p>` element.
+ paragraph: {
+ content: "inline*",
+ group: "block",
+ parseDOM: [{ tag: "p" }],
+ toDOM() { return pDOM; }
+ },
+
+ // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
+ blockquote: {
+ content: "block+",
+ group: "block",
+ defining: true,
+ parseDOM: [{ tag: "blockquote" }],
+ toDOM() { return blockquoteDOM; }
+ },
+
+ // :: NodeSpec A horizontal rule (`<hr>`).
+ horizontal_rule: {
+ group: "block",
+ parseDOM: [{ tag: "hr" }],
+ toDOM() { return hrDOM; }
+ },
+
+ // :: NodeSpec A heading textblock, with a `level` attribute that
+ // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
+ // `<h6>` elements.
+ heading: {
+ attrs: { level: { default: 1 } },
+ content: "inline*",
+ group: "block",
+ defining: true,
+ parseDOM: [{ tag: "h1", attrs: { level: 1 } },
+ { tag: "h2", attrs: { level: 2 } },
+ { tag: "h3", attrs: { level: 3 } },
+ { tag: "h4", attrs: { level: 4 } },
+ { tag: "h5", attrs: { level: 5 } },
+ { tag: "h6", attrs: { level: 6 } }],
+ toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
+ },
+
+ // :: NodeSpec A code listing. Disallows marks or non-text inline
+ // nodes by default. Represented as a `<pre>` element with a
+ // `<code>` element inside of it.
+ code_block: {
+ content: "text*",
+ marks: "",
+ group: "block",
+ code: true,
+ defining: true,
+ parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
+ toDOM() { return preDOM; }
+ },
+
+ // :: NodeSpec The text node.
+ text: {
+ group: "inline"
+ },
+
+ // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
+ // `alt`, and `href` attributes. The latter two default to the empty
+ // string.
+ image: {
+ inline: true,
+ attrs: {
+ src: {},
+ width: { default: "100px" },
+ alt: { default: null },
+ title: { default: null }
+ },
+ group: "inline",
+ draggable: true,
+ parseDOM: [{
+ tag: "img[src]", getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute("src"),
+ title: dom.getAttribute("title"),
+ alt: dom.getAttribute("alt"),
+ width: Math.min(100, Number(dom.getAttribute("width"))),
+ };
+ }
+ }],
+ // TODO if we don't define toDom, something weird happens: dragging the image will not move it but clone it. Why?
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}` };
+ return ["img", { ...node.attrs, ...attrs }];
}
- }
- }],
- toDOM(node: any) { return ["img", node.attrs] }
- },
-
- // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
- hard_break: {
- inline: true,
- group: "inline",
- selectable: false,
- parseDOM: [{ tag: "br" }],
- toDOM() { return brDOM }
- },
-
- ordered_list: {
- ...orderedList,
- content: 'list_item+',
- group: 'block'
- },
- bullet_list: {
- content: 'list_item+',
- group: 'block',
- parseDOM: [{ tag: "ul" }, { style: "list-style-type=disc;" }],
- toDOM() { return ulDOM }
- },
- list_item: {
- ...listItem,
- content: 'paragraph block*'
- }
-}
+ },
+
+ // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
+ hard_break: {
+ inline: true,
+ group: "inline",
+ selectable: false,
+ parseDOM: [{ tag: "br" }],
+ toDOM() { return brDOM; }
+ },
+
+ ordered_list: {
+ ...orderedList,
+ content: 'list_item+',
+ group: 'block'
+ },
+ //this doesn't currently work for some reason
+ bullet_list: {
+ ...bulletList,
+ content: 'list_item+',
+ group: 'block',
+ // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
+ // toDOM() { return ulDOM }
+ },
+ //bullet_list: {
+ // content: 'list_item+',
+ // group: 'block',
+ //active: blockActive(schema.nodes.bullet_list),
+ //enable: wrapInList(schema.nodes.bullet_list),
+ //run: wrapInList(schema.nodes.bullet_list),
+ //select: state => true,
+ // },
+ list_item: {
+ ...listItem,
+ content: 'paragraph block*'
+ }
+};
const emDOM: DOMOutputSpecArray = ["em", 0];
const strongDOM: DOMOutputSpecArray = ["strong", 0];
@@ -132,86 +150,263 @@ const underlineDOM: DOMOutputSpecArray = ["underline", 0];
// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks: { [index: string]: MarkSpec } = {
- // :: MarkSpec A link. Has `href` and `title` attributes. `title`
- // defaults to the empty string. Rendered and parsed as an `<a>`
- // element.
- link: {
- attrs: {
- href: {},
- title: { default: null }
- },
- inclusive: false,
- parseDOM: [{
- tag: "a[href]", getAttrs(dom: any) {
- return { href: dom.getAttribute("href"), title: dom.getAttribute("title") }
- }
- }],
- toDOM(node: any) { return ["a", node.attrs, 0] }
- },
-
- // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
- // Has parse rules that also match `<i>` and `font-style: italic`.
- em: {
- parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
- toDOM() { return emDOM }
- },
-
- // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
- // also match `<b>` and `font-weight: bold`.
- strong: {
- parseDOM: [{ tag: "strong" },
- { tag: "b" },
- { style: "font-weight" }],
- toDOM() { return strongDOM }
- },
-
- underline: {
- parseDOM: [
- { tag: 'u' },
- { style: 'text-decoration=underline' }
- ],
- toDOM: () => ['span', {
- style: 'text-decoration:underline'
- }]
- },
-
- strikethrough: {
- parseDOM: [
- { tag: 'strike' },
- { style: 'text-decoration=line-through' },
- { style: 'text-decoration-line=line-through' }
- ],
- toDOM: () => ['span', {
- style: 'text-decoration-line:line-through'
- }]
- },
-
- subscript: {
- excludes: 'superscript',
- parseDOM: [
- { tag: 'sub' },
- { style: 'vertical-align=sub' }
- ],
- toDOM: () => ['sub']
- },
-
- superscript: {
- excludes: 'subscript',
- parseDOM: [
- { tag: 'sup' },
- { style: 'vertical-align=super' }
- ],
- toDOM: () => ['sup']
- },
-
-
- // :: MarkSpec Code font mark. Represented as a `<code>` element.
- code: {
- parseDOM: [{ tag: "code" }],
- toDOM() { return codeDOM }
- }
+ // :: MarkSpec A link. Has `href` and `title` attributes. `title`
+ // defaults to the empty string. Rendered and parsed as an `<a>`
+ // element.
+ link: {
+ attrs: {
+ href: {},
+ title: { default: null }
+ },
+ inclusive: false,
+ parseDOM: [{
+ tag: "a[href]", getAttrs(dom: any) {
+ return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
+ }
+ }],
+ toDOM(node: any) { return ["a", node.attrs, 0]; }
+ },
+
+ // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
+ // Has parse rules that also match `<i>` and `font-style: italic`.
+ em: {
+ parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
+ toDOM() { return emDOM; }
+ },
+
+ // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
+ // also match `<b>` and `font-weight: bold`.
+ strong: {
+ parseDOM: [{ tag: "strong" },
+ { tag: "b" },
+ { style: "font-weight" }],
+ toDOM() { return strongDOM; }
+ },
+
+ underline: {
+ parseDOM: [
+ { tag: 'u' },
+ { style: 'text-decoration=underline' }
+ ],
+ toDOM: () => ['span', {
+ style: 'text-decoration:underline'
+ }]
+ },
+
+ strikethrough: {
+ parseDOM: [
+ { tag: 'strike' },
+ { style: 'text-decoration=line-through' },
+ { style: 'text-decoration-line=line-through' }
+ ],
+ toDOM: () => ['span', {
+ style: 'text-decoration-line:line-through'
+ }]
+ },
+
+ subscript: {
+ excludes: 'superscript',
+ parseDOM: [
+ { tag: 'sub' },
+ { style: 'vertical-align=sub' }
+ ],
+ toDOM: () => ['sub']
+ },
+
+ superscript: {
+ excludes: 'subscript',
+ parseDOM: [
+ { tag: 'sup' },
+ { style: 'vertical-align=super' }
+ ],
+ toDOM: () => ['sup']
+ },
+
+
+ // :: MarkSpec Code font mark. Represented as a `<code>` element.
+ code: {
+ parseDOM: [{ tag: "code" }],
+ toDOM() { return codeDOM; }
+ },
+
+
+ /* FONTS */
+ timesNewRoman: {
+ parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: "Times New Roman", Times, serif;'
+ }]
+ },
+
+ arial: {
+ parseDOM: [{ style: 'font-family: Arial, Helvetica, sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: Arial, Helvetica, sans-serif;'
+ }]
+ },
+
+ georgia: {
+ parseDOM: [{ style: 'font-family: Georgia, serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: Georgia, serif;'
+ }]
+ },
+
+ comicSans: {
+ parseDOM: [{ style: 'font-family: "Comic Sans MS", cursive, sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: "Comic Sans MS", cursive, sans-serif;'
+ }]
+ },
+
+ tahoma: {
+ parseDOM: [{ style: 'font-family: Tahoma, Geneva, sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: Tahoma, Geneva, sans-serif;'
+ }]
+ },
+
+ impact: {
+ parseDOM: [{ style: 'font-family: Impact, Charcoal, sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: Impact, Charcoal, sans-serif;'
+ }]
+ },
+
+ crimson: {
+ parseDOM: [{ style: 'font-family: "Crimson Text", sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: "Crimson Text", sans-serif;'
+ }]
+ },
+
+ /** FONT SIZES */
+
+ p10: {
+ parseDOM: [{ style: 'font-size: 10px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 10px;'
+ }]
+ },
+
+ p12: {
+ parseDOM: [{ style: 'font-size: 12px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 12px;'
+ }]
+ },
+
+ p14: {
+ parseDOM: [{ style: 'font-size: 14px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 14px;'
+ }]
+ },
+
+ p16: {
+ parseDOM: [{ style: 'font-size: 16px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 16px;'
+ }]
+ },
+
+ p24: {
+ parseDOM: [{ style: 'font-size: 24px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 24px;'
+ }]
+ },
+
+ p32: {
+ parseDOM: [{ style: 'font-size: 32px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 32px;'
+ }]
+ },
+
+ p48: {
+ parseDOM: [{ style: 'font-size: 48px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 48px;'
+ }]
+ },
+
+ p72: {
+ parseDOM: [{ style: 'font-size: 72px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 72px;'
+ }]
+ },
+};
+function getFontSize(element: any) {
+ return parseFloat((getComputedStyle(element) as any).fontSize);
}
+export class ImageResizeView {
+ _handle: HTMLElement;
+ _img: HTMLElement;
+ _outer: HTMLElement;
+ constructor(node: any, view: any, getPos: any) {
+ this._handle = document.createElement("span");
+ this._img = document.createElement("img");
+ this._outer = document.createElement("span");
+ this._outer.style.position = "relative";
+ this._outer.style.width = node.attrs.width;
+ this._outer.style.display = "inline-block";
+ this._outer.style.overflow = "hidden";
+
+ this._img.setAttribute("src", node.attrs.src);
+ this._img.style.width = "100%";
+ this._handle.style.position = "absolute";
+ this._handle.style.width = "20px";
+ this._handle.style.height = "20px";
+ this._handle.style.backgroundColor = "blue";
+ this._handle.style.borderRadius = "15px";
+ this._handle.style.display = "none";
+ this._handle.style.bottom = "-10px";
+ this._handle.style.right = "-10px";
+ let self = this;
+ this._handle.onpointerdown = function (e: any) {
+ e.preventDefault();
+ e.stopPropagation();
+ const startX = e.pageX;
+ const startWidth = parseFloat(node.attrs.width);
+ const onpointermove = (e: any) => {
+ const currentX = e.pageX;
+ const diffInPx = currentX - startX;
+ self._outer.style.width = `${startWidth + diffInPx}`;
+ };
+
+ const onpointerup = () => {
+ document.removeEventListener("pointermove", onpointermove);
+ document.removeEventListener("pointerup", onpointerup);
+ view.dispatch(
+ view.state.tr.setNodeMarkup(getPos(), null,
+ { src: node.attrs.src, width: self._outer.style.width })
+ .setSelection(view.state.selection));
+ };
+
+ document.addEventListener("pointermove", onpointermove);
+ document.addEventListener("pointerup", onpointerup);
+ };
+
+ this._outer.appendChild(this._handle);
+ this._outer.appendChild(this._img);
+ (this as any).dom = this._outer;
+ }
+
+ selectNode() {
+ this._img.classList.add("ProseMirror-selectednode");
+
+ this._handle.style.display = "";
+ }
+
+ deselectNode() {
+ this._img.classList.remove("ProseMirror-selectednode");
+
+ this._handle.style.display = "none";
+ }
+}
// :: Schema
// This schema rougly corresponds to the document schema used by
// [CommonMark](http://commonmark.org/), minus the list elements,
@@ -220,4 +415,4 @@ export const marks: { [index: string]: MarkSpec } = {
//
// To reuse elements from this schema, extend or read from its
// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
-export const schema = new Schema({ nodes, marks }) \ No newline at end of file
+export const schema = new Schema({ nodes, marks }); \ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 46bd1a206..e45f61c11 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -1,56 +1,76 @@
// import * as ts from "typescript"
let ts = (window as any).ts;
-import { Opt, Field } from "../../fields/Field";
-import { Document } from "../../fields/Document";
-import { NumberField } from "../../fields/NumberField";
-import { ImageField } from "../../fields/ImageField";
-import { TextField } from "../../fields/TextField";
-import { RichTextField } from "../../fields/RichTextField";
-import { KeyStore } from "../../fields/KeyStore";
-import { ListField } from "../../fields/ListField";
// // @ts-ignore
// import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
// // @ts-ignore
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
// @ts-ignore
-import * as typescriptlib from '!!raw-loader!./type_decls.d'
+import * as typescriptlib from '!!raw-loader!./type_decls.d';
+import { Docs } from "../documents/Documents";
+import { Doc, Field } from '../../new_fields/Doc';
+import { ImageField, PdfField, VideoField, AudioField } from '../../new_fields/URLField';
+import { List } from '../../new_fields/List';
+import { RichTextField } from '../../new_fields/RichTextField';
+export interface ScriptSucccess {
+ success: true;
+ result: any;
+}
+
+export interface ScriptError {
+ success: false;
+ error: any;
+}
-export interface ExecutableScript {
- (): any;
+export type ScriptResult = ScriptSucccess | ScriptError;
+
+export interface CompiledScript {
+ readonly compiled: true;
+ readonly originalScript: string;
+ readonly options: Readonly<ScriptOptions>;
+ run(args?: { [name: string]: any }): ScriptResult;
+}
- compiled: boolean;
+export interface CompileError {
+ compiled: false;
+ errors: any[];
}
-function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [name: string]: any }): ExecutableScript {
- const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
+export type CompileResult = CompiledScript | CompileError;
- let func: () => Opt<Field>;
- if (compiled && script) {
- let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField];
- let paramNames = ["KeyStore", ...fieldTypes.map(fn => fn.name)];
- let params: any[] = [KeyStore, ...fieldTypes]
- for (let prop in scope) {
- if (prop === "this") {
+function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
+ const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
+ if (errors || !script) {
+ return { compiled: false, errors: diagnostics };
+ }
+
+ let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField];
+ let paramNames = ["Docs", ...fieldTypes.map(fn => fn.name)];
+ let params: any[] = [Docs, ...fieldTypes];
+ let compiledFunction = new Function(...paramNames, `return ${script}`);
+ let { capturedVariables = {} } = options;
+ let run = (args: { [name: string]: any } = {}): ScriptResult => {
+ let argsArray: any[] = [];
+ for (let name of customParams) {
+ if (name === "this") {
continue;
}
- paramNames.push(prop);
- params.push(scope[prop]);
+ if (name in args) {
+ argsArray.push(args[name]);
+ } else {
+ argsArray.push(capturedVariables[name]);
+ }
}
- let thisParam = scope["this"];
- let compiledFunction = new Function(...paramNames, script);
- func = function (): Opt<Field> {
- return compiledFunction.apply(thisParam, params)
- };
- } else {
- func = () => undefined;
- }
-
- return Object.assign(func,
- {
- compiled
- });
+ let thisParam = args.this || capturedVariables.this;
+ try {
+ const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
+ return { success: true, result };
+ } catch (error) {
+ return { success: false, error };
+ }
+ };
+ return { compiled: true, run, originalScript, options };
}
interface File {
@@ -72,14 +92,14 @@ class ScriptingCompilerHost {
}
// getDefaultLibFileName(options: ts.CompilerOptions): string {
getDefaultLibFileName(options: any): string {
- return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means...
+ return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
}
writeFile(fileName: string, content: string) {
const file = this.files.find(file => file.fileName === fileName);
if (file) {
file.content = content;
} else {
- this.files.push({ fileName, content })
+ this.files.push({ fileName, content });
}
}
getCurrentDirectory(): string {
@@ -106,27 +126,44 @@ class ScriptingCompilerHost {
}
}
-export function CompileScript(script: string, scope?: { [name: string]: any }, addReturn: boolean = false): ExecutableScript {
+export interface ScriptOptions {
+ requiredType?: string;
+ addReturn?: boolean;
+ params?: { [name: string]: string };
+ capturedVariables?: { [name: string]: Field };
+}
+
+export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
+ const { requiredType = "", addReturn = false, params = {}, capturedVariables = {} } = options;
let host = new ScriptingCompilerHost;
- let funcScript = `(function() {
+ let paramNames: string[] = [];
+ if ("this" in params || "this" in capturedVariables) {
+ paramNames.push("this");
+ }
+ for (const key in params) {
+ if (key === "this") continue;
+ paramNames.push(key);
+ }
+ let paramList = paramNames.map(key => {
+ const val = params[key];
+ return `${key}: ${val}`;
+ });
+ for (const key in capturedVariables) {
+ if (key === "this") continue;
+ paramNames.push(key);
+ paramList.push(`${key}: ${capturedVariables[key].constructor.name}`);
+ }
+ let paramString = paramList.join(", ");
+ let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
${addReturn ? `return ${script};` : script}
- })()`
+ })`;
host.writeFile("file.ts", funcScript);
host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
let program = ts.createProgram(["file.ts"], {}, host);
let testResult = program.emit();
- let outputText = "return " + host.readFile("file.js");
+ let outputText = host.readFile("file.js");
let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
- return Compile(outputText, diagnostics, scope || {});
-}
-
-export function ToField(data: any): Opt<Field> {
- if (typeof data == "string") {
- return new TextField(data);
- } else if (typeof data == "number") {
- return new NumberField(data);
- }
- return undefined;
+ return Run(outputText, paramNames, diagnostics, script, options);
} \ No newline at end of file
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
index b6b088170..a209874a3 100644
--- a/src/client/util/ScrollBox.tsx
+++ b/src/client/util/ScrollBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
export class ScrollBox extends React.Component {
onWheel = (e: React.WheelEvent) => {
@@ -16,6 +16,6 @@ export class ScrollBox extends React.Component {
}} onWheel={this.onWheel}>
{this.props.children}
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
new file mode 100644
index 000000000..4ccff0d1b
--- /dev/null
+++ b/src/client/util/SearchUtil.ts
@@ -0,0 +1,25 @@
+import * as rp from 'request-promise';
+import { DocServer } from '../DocServer';
+import { Doc } from '../../new_fields/Doc';
+import { Id } from '../../new_fields/RefField';
+
+export namespace SearchUtil {
+ export function Search(query: string, returnDocs: true): Promise<Doc[]>;
+ export function Search(query: string, returnDocs: false): Promise<string[]>;
+ export async function Search(query: string, returnDocs: boolean) {
+ const ids = JSON.parse(await rp.get(DocServer.prepend("/search"), {
+ qs: { query }
+ }));
+ if (!returnDocs) {
+ return ids;
+ }
+ const docMap = await DocServer.GetRefFields(ids);
+ return ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
+ }
+
+ export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]> {
+ const proto = await Doc.GetT(doc, "proto", Doc, true);
+ const protoId = (proto || doc)[Id];
+ return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
+ }
+} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1a711ae64..8c92c2023 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,5 +1,8 @@
import { observable, action } from "mobx";
+import { Doc } from "../../new_fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { NumCast } from "../../new_fields/Types";
export namespace SelectionManager {
class Manager {
@@ -10,30 +13,76 @@ export namespace SelectionManager {
SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
if (!ctrlPressed) {
- manager.SelectedDocuments = [];
+ this.DeselectAll();
}
if (manager.SelectedDocuments.indexOf(doc) === -1) {
- manager.SelectedDocuments.push(doc)
+ manager.SelectedDocuments.push(doc);
+ doc.props.whenActiveChanged(true);
}
}
+
+ @action
+ DeselectAll(): void {
+ manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
+ manager.SelectedDocuments = [];
+ FormattedTextBox.InputBoxOverlay = undefined;
+ }
+ @action
+ ReselectAll() {
+ let sdocs = manager.SelectedDocuments.map(d => d);
+ manager.SelectedDocuments = [];
+ return sdocs;
+ }
+ @action
+ ReselectAll2(sdocs: DocumentView[]) {
+ sdocs.map(s => SelectionManager.SelectDoc(s, true));
+ }
}
- const manager = new Manager;
+ const manager = new Manager();
export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
- manager.SelectDoc(doc, ctrlPressed)
+ manager.SelectDoc(doc, ctrlPressed);
}
export function IsSelected(doc: DocumentView): boolean {
return manager.SelectedDocuments.indexOf(doc) !== -1;
}
- export function DeselectAll(): void {
- manager.SelectedDocuments = []
+ export function DeselectAll(except?: Doc): void {
+ let found: DocumentView | undefined = undefined;
+ if (except) {
+ for (const view of manager.SelectedDocuments) {
+ if (view.props.Document === except) found = view;
+ }
+ }
+
+ manager.DeselectAll();
+ if (found) manager.SelectDoc(found, false);
}
+ export function ReselectAll() {
+ let sdocs = manager.ReselectAll();
+ setTimeout(() => manager.ReselectAll2(sdocs), 0);
+ }
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
}
-} \ No newline at end of file
+ export function ViewsSortedVertically(): DocumentView[] {
+ let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
+ if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
+ if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
+ return 0;
+ });
+ return sorted;
+ }
+ export function ViewsSortedHorizontally(): DocumentView[] {
+ let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
+ if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1;
+ if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1;
+ return 0;
+ });
+ return sorted;
+ }
+}
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
new file mode 100644
index 000000000..7ded85e43
--- /dev/null
+++ b/src/client/util/SerializationHelper.ts
@@ -0,0 +1,130 @@
+import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
+import { Field } from "../../new_fields/Doc";
+
+export namespace SerializationHelper {
+ let serializing: number = 0;
+ export function IsSerializing() {
+ return serializing > 0;
+ }
+
+ export function Serialize(obj: Field): any {
+ if (obj === undefined || obj === null) {
+ return undefined;
+ }
+
+ if (typeof obj !== 'object') {
+ return obj;
+ }
+
+ serializing += 1;
+ if (!(obj.constructor.name in reverseMap)) {
+ throw Error(`type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
+ }
+
+ const json = serialize(obj);
+ json.__type = reverseMap[obj.constructor.name];
+ serializing -= 1;
+ return json;
+ }
+
+ export function Deserialize(obj: any): any {
+ if (obj === undefined || obj === null) {
+ return undefined;
+ }
+
+ if (typeof obj !== 'object') {
+ return obj;
+ }
+
+ serializing += 1;
+ if (!obj.__type) {
+ throw Error("No property 'type' found in JSON.");
+ }
+
+ if (!(obj.__type in serializationTypes)) {
+ throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`);
+ }
+
+ const value = deserialize(serializationTypes[obj.__type], obj);
+ serializing -= 1;
+ return value;
+ }
+}
+
+let serializationTypes: { [name: string]: any } = {};
+let reverseMap: { [ctor: string]: string } = {};
+
+export interface DeserializableOpts {
+ (constructor: { new(...args: any[]): any }): void;
+ withFields(fields: string[]): Function;
+}
+
+export function Deserializable(name: string): DeserializableOpts;
+export function Deserializable(constructor: { new(...args: any[]): any }): void;
+export function Deserializable(constructor: { new(...args: any[]): any } | string): DeserializableOpts | void {
+ function addToMap(name: string, ctor: { new(...args: any[]): any }) {
+ const schema = getDefaultModelSchema(ctor) as any;
+ if (schema.targetClass !== ctor) {
+ const newSchema = { ...schema, factory: () => new ctor() };
+ setDefaultModelSchema(ctor, newSchema);
+ }
+ if (!(name in serializationTypes)) {
+ serializationTypes[name] = ctor;
+ reverseMap[ctor.name] = name;
+ } else {
+ throw new Error(`Name ${name} has already been registered as deserializable`);
+ }
+ }
+ if (typeof constructor === "string") {
+ return Object.assign((ctor: { new(...args: any[]): any }) => {
+ addToMap(constructor, ctor);
+ }, { withFields: Deserializable.withFields });
+ }
+ addToMap(constructor.name, constructor);
+}
+
+export namespace Deserializable {
+ export function withFields(fields: string[]) {
+ return function (constructor: { new(...fields: any[]): any }) {
+ Deserializable(constructor);
+ let schema = getDefaultModelSchema(constructor);
+ if (schema) {
+ schema.factory = context => {
+ const args = fields.map(key => context.json[key]);
+ return new constructor(...args);
+ };
+ // TODO A modified version of this would let us not reassign fields that we're passing into the constructor later on in deserializing
+ // fields.forEach(field => {
+ // if (field in schema.props) {
+ // let propSchema = schema.props[field];
+ // if (propSchema === false) {
+ // return;
+ // } else if (propSchema === true) {
+ // propSchema = primitive();
+ // }
+ // schema.props[field] = custom(propSchema.serializer,
+ // () => {
+ // return SKIP;
+ // });
+ // }
+ // });
+ } else {
+ schema = {
+ props: {},
+ factory: context => {
+ const args = fields.map(key => context.json[key]);
+ return new constructor(...args);
+ }
+ };
+ setDefaultModelSchema(constructor, schema);
+ }
+ };
+ }
+}
+
+export function autoObject(): PropSchema {
+ return custom(
+ (s) => SerializationHelper.Serialize(s),
+ (s) => SerializationHelper.Deserialize(s)
+ );
+} \ No newline at end of file
diff --git a/src/client/util/TooltipLinkingMenu.tsx b/src/client/util/TooltipLinkingMenu.tsx
new file mode 100644
index 000000000..55e0eb909
--- /dev/null
+++ b/src/client/util/TooltipLinkingMenu.tsx
@@ -0,0 +1,86 @@
+import { action, IReactionDisposer, reaction } from "mobx";
+import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css
+import { baseKeymap, lift } from "prosemirror-commands";
+import { history, redo, undo } from "prosemirror-history";
+import { keymap } from "prosemirror-keymap";
+import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
+import { EditorView } from "prosemirror-view";
+import { schema } from "./RichTextSchema";
+import { Schema, NodeType, MarkType } from "prosemirror-model";
+import React = require("react");
+import "./TooltipTextMenu.scss";
+const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { wrapInList, bulletList, liftListItem, listItem } from 'prosemirror-schema-list';
+import {
+ faListUl,
+} from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { FieldViewProps } from "../views/nodes/FieldView";
+import { throwStatement } from "babel-types";
+
+const SVG = "http://www.w3.org/2000/svg";
+
+//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
+export class TooltipLinkingMenu {
+
+ private tooltip: HTMLElement;
+ private view: EditorView;
+ private editorProps: FieldViewProps;
+
+ constructor(view: EditorView, editorProps: FieldViewProps) {
+ this.view = view;
+ this.editorProps = editorProps;
+ this.tooltip = document.createElement("div");
+ this.tooltip.className = "tooltipMenu linking";
+
+ //add the div which is the tooltip
+ view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
+
+ let target = "https://www.google.com";
+
+ let link = document.createElement("a");
+ link.href = target;
+ link.textContent = target;
+ link.target = "_blank";
+ link.style.color = "white";
+ this.tooltip.appendChild(link);
+
+ this.update(view, undefined);
+ }
+
+ //updates the tooltip menu when the selection changes
+ update(view: EditorView, lastState: EditorState | undefined) {
+ let state = view.state;
+ // Don't do anything if the document/selection didn't change
+ if (lastState && lastState.doc.eq(state.doc) &&
+ lastState.selection.eq(state.selection)) return;
+
+ // Hide the tooltip if the selection is empty
+ if (state.selection.empty) {
+ this.tooltip.style.display = "none";
+ return;
+ }
+
+ console.log("STORED:");
+ console.log(state.doc.content.firstChild!.content);
+
+ // Otherwise, reposition it and update its content
+ this.tooltip.style.display = "";
+ let { from, to } = state.selection;
+ let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
+ // The box in which the tooltip is positioned, to use as base
+ let box = this.tooltip.offsetParent!.getBoundingClientRect();
+ // Find a center-ish x position from the selection endpoints (when
+ // crossing lines, end may be more to the left)
+ let left = Math.max((start.left + end.left) / 2, start.left + 3);
+ this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
+ let mid = Math.min(start.left, end.left) + width;
+
+ this.tooltip.style.width = "auto";
+ this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ }
+
+ destroy() { this.tooltip.remove(); }
+}
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index fa43f5326..437da0d63 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -1,14 +1,260 @@
+@import "../views/globalCssVariables";
+
+.ProseMirror-textblock-dropdown {
+ min-width: 3em;
+ }
+
+ .ProseMirror-menu {
+ margin: 0 -4px;
+ line-height: 1;
+ }
+
+ .ProseMirror-tooltip .ProseMirror-menu {
+ width: -webkit-fit-content;
+ width: fit-content;
+ white-space: pre;
+ }
+
+ .ProseMirror-menuitem {
+ margin-right: 3px;
+ display: inline-block;
+ }
+
+ .ProseMirror-menuseparator {
+ // border-right: 1px solid #ddd;
+ margin-right: 3px;
+ }
+
+ .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu {
+ font-size: 90%;
+ white-space: nowrap;
+ }
+
+ .ProseMirror-menu-dropdown {
+ vertical-align: 1px;
+ cursor: pointer;
+ position: relative;
+ padding-right: 15px;
+ margin: 3px;
+ background: #333333;
+ border-radius: 3px;
+ text-align: center;
+ }
+
+ .ProseMirror-menu-dropdown-wrap {
+ padding: 1px 0 1px 4px;
+ display: inline-block;
+ position: relative;
+ }
+
+ .ProseMirror-menu-dropdown:after {
+ content: "";
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 4px solid currentColor;
+ opacity: .6;
+ position: absolute;
+ right: 4px;
+ top: calc(50% - 2px);
+ }
+
+ .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
+ position: absolute;
+ background: $dark-color;
+ color:white;
+ border: 1px solid rgb(223, 223, 223);
+ padding: 2px;
+ }
+
+ .ProseMirror-menu-dropdown-menu {
+ z-index: 15;
+ min-width: 6em;
+ }
+
+ .linking {
+ text-align: center;
+ }
+
+ .ProseMirror-menu-dropdown-item {
+ cursor: pointer;
+ padding: 2px 8px 2px 4px;
+ width: auto;
+ }
+
+ .ProseMirror-menu-dropdown-item:hover {
+ background: #2e2b2b;
+ }
+
+ .ProseMirror-menu-submenu-wrap {
+ position: relative;
+ margin-right: -4px;
+ }
+
+ .ProseMirror-menu-submenu-label:after {
+ content: "";
+ border-top: 4px solid transparent;
+ border-bottom: 4px solid transparent;
+ border-left: 4px solid currentColor;
+ opacity: .6;
+ position: absolute;
+ right: 4px;
+ top: calc(50% - 4px);
+ }
+
+ .ProseMirror-menu-submenu {
+ display: none;
+ min-width: 4em;
+ left: 100%;
+ top: -3px;
+ }
+
+ .ProseMirror-menu-active {
+ background: #eee;
+ border-radius: 4px;
+ }
+
+ .ProseMirror-menu-active {
+ background: #eee;
+ border-radius: 4px;
+ }
+
+ .ProseMirror-menu-disabled {
+ opacity: .3;
+ }
+
+ .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
+ display: block;
+ }
+
+ .ProseMirror-menubar {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+ position: relative;
+ min-height: 1em;
+ color: white;
+ padding: 1px 6px;
+ top: 0; left: 0; right: 0;
+ border-bottom: 1px solid silver;
+ background:$dark-color;
+ z-index: 10;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ overflow: visible;
+ }
+
+ .ProseMirror-icon {
+ display: inline-block;
+ line-height: .8;
+ vertical-align: -2px; /* Compensate for padding */
+ padding: 2px 8px;
+ cursor: pointer;
+ }
+
+ .ProseMirror-menu-disabled.ProseMirror-icon {
+ cursor: default;
+ }
+
+ .ProseMirror-icon svg {
+ fill: currentColor;
+ height: 1em;
+ }
+
+ .ProseMirror-icon span {
+ vertical-align: text-top;
+ }
+
+ .ProseMirror ul, .ProseMirror ol {
+ padding-left: 30px;
+ }
+
+ .ProseMirror blockquote {
+ padding-left: 1em;
+ border-left: 3px solid #eee;
+ margin-left: 0; margin-right: 0;
+ }
+
+ .ProseMirror-example-setup-style img {
+ cursor: default;
+ }
+
+ .ProseMirror-prompt {
+ background: white;
+ padding: 5px 10px 5px 15px;
+ border: 1px solid silver;
+ position: fixed;
+ border-radius: 3px;
+ z-index: 11;
+ box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2);
+ }
+
+ .ProseMirror-prompt h5 {
+ margin: 0;
+ font-weight: normal;
+ font-size: 100%;
+ color: #444;
+ }
+
+ .ProseMirror-prompt input[type="text"],
+ .ProseMirror-prompt textarea {
+ background: #eee;
+ border: none;
+ outline: none;
+ }
+
+ .ProseMirror-prompt input[type="text"] {
+ padding: 0 4px;
+ }
+
+ .ProseMirror-prompt-close {
+ position: absolute;
+ left: 2px; top: 1px;
+ color: #666;
+ border: none; background: transparent; padding: 0;
+ }
+
+ .ProseMirror-prompt-close:after {
+ content: "✕";
+ font-size: 12px;
+ }
+
+ .ProseMirror-invalid {
+ background: #ffc;
+ border: 1px solid #cc7;
+ border-radius: 4px;
+ padding: 5px 10px;
+ position: absolute;
+ min-width: 10em;
+ }
+
+ .ProseMirror-prompt-buttons {
+ margin-top: 5px;
+ display: none;
+ }
.tooltipMenu {
position: absolute;
- z-index: 20;
- background: rgb(19, 18, 18);
+ z-index: 200;
+ background: $dark-color;
border: 1px solid silver;
border-radius: 4px;
padding: 2px 10px;
margin-bottom: 7px;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
+ pointer-events: all;
+ .ProseMirror-example-setup-style hr {
+ padding: 2px 10px;
+ border: none;
+ margin: 1em 0;
+ }
+
+ .ProseMirror-example-setup-style hr:after {
+ content: "";
+ display: block;
+ height: 1px;
+ background-color: silver;
+ line-height: 2px;
+ }
}
.tooltipMenu:before {
@@ -31,7 +277,7 @@
bottom: -4.5px;
border: 5px solid transparent;
border-bottom-width: 0;
- border-top-color: black;
+ border-top-color: $dark-color;
}
.menuicon {
@@ -51,4 +297,8 @@
.underline {text-decoration: underline}
.superscript {vertical-align:super}
.subscript { vertical-align:sub }
- .strikethrough {text-decoration-line:line-through} \ No newline at end of file
+ .strikethrough {text-decoration-line:line-through}
+ .font-size-indicator {
+ font-size: 12px;
+ padding-right: 0px;
+ }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 3b87fe9de..4d40d09b2 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,125 +1,478 @@
import { action, IReactionDisposer, reaction } from "mobx";
-import { baseKeymap } from "prosemirror-commands";
+import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css
+import { baseKeymap, lift } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
-const { exampleSetup } = require("prosemirror-example-setup")
-import { EditorState, Transaction, } from "prosemirror-state";
+import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "./RichTextSchema";
-import React = require("react")
+import { Schema, NodeType, MarkType, Mark } from "prosemirror-model";
+import React = require("react");
import "./TooltipTextMenu.scss";
const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
-import { library } from '@fortawesome/fontawesome-svg-core'
-import { wrapInList, bulletList } from 'prosemirror-schema-list'
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list';
+import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform';
import {
- faListUl,
+ faListUl,
} from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { FieldViewProps } from "../views/nodes/FieldView";
+import { throwStatement } from "babel-types";
+import { View } from "@react-pdf/renderer";
+import { DragManager } from "./DragManager";
+import { Doc, Opt, Field } from "../../new_fields/Doc";
+import { Id } from "../../new_fields/RefField";
+import { Utils } from "../northstar/utils/Utils";
+import { DocServer } from "../DocServer";
+import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { DocumentManager } from "./DocumentManager";
+const SVG = "http://www.w3.org/2000/svg";
-
+//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipTextMenu {
- private tooltip: HTMLElement;
-
- constructor(view: EditorView) {
- this.tooltip = document.createElement("div");
- this.tooltip.className = "tooltipMenu";
-
- //add the div which is the tooltip
- view.dom.parentNode!.appendChild(this.tooltip);
-
- //add additional icons
- library.add(faListUl);
-
- //add the buttons to the tooltip
- let items = [
- { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") },
- { command: toggleMark(schema.marks.em), dom: this.icon("i", "em") },
- { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline") },
- { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
- { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
- { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
- { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }
- ]
- items.forEach(({ dom }) => this.tooltip.appendChild(dom));
-
- //pointer down handler to activate button effects
- this.tooltip.addEventListener("pointerdown", e => {
- e.preventDefault();
- view.focus();
- items.forEach(({ command, dom }) => {
- if (dom.contains(e.srcElement)) {
- command(view.state, view.dispatch, view)
+ public tooltip: HTMLElement;
+ private num_icons = 0;
+ private view: EditorView;
+ private fontStyles: MarkType[];
+ private fontSizes: MarkType[];
+ private listTypes: NodeType[];
+ private editorProps: FieldViewProps;
+ private state: EditorState;
+ private fontSizeToNum: Map<MarkType, number>;
+ private fontStylesToName: Map<MarkType, string>;
+ private listTypeToIcon: Map<NodeType, string>;
+ private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
+ private linkEditor?: HTMLDivElement;
+ private linkText?: HTMLDivElement;
+ private linkDrag?: HTMLImageElement;
+ //dropdown doms
+ private fontSizeDom?: Node;
+ private fontStyleDom?: Node;
+ private listTypeBtnDom?: Node;
+
+ constructor(view: EditorView, editorProps: FieldViewProps) {
+ this.view = view;
+ this.state = view.state;
+ this.editorProps = editorProps;
+ this.tooltip = document.createElement("div");
+ this.tooltip.className = "tooltipMenu";
+
+ //add the div which is the tooltip
+ view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
+
+ //add additional icons
+ library.add(faListUl);
+ //add the buttons to the tooltip
+ let items = [
+ { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") },
+ { command: toggleMark(schema.marks.em), dom: this.icon("i", "em") },
+ { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline") },
+ { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
+ { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
+ { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
+ // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
+ // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
+ // { command: lift, dom: this.icon("<", "lift") },
+ ];
+ //add menu items
+ items.forEach(({ dom, command }) => {
+ this.tooltip.appendChild(dom);
+
+ //pointer down handler to activate button effects
+ dom.addEventListener("pointerdown", e => {
+ e.preventDefault();
+ view.focus();
+ command(view.state, view.dispatch, view);
+ });
+
+ });
+
+ //list of font styles
+ this.fontStylesToName = new Map();
+ this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman");
+ this.fontStylesToName.set(schema.marks.arial, "Arial");
+ this.fontStylesToName.set(schema.marks.georgia, "Georgia");
+ this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans MS");
+ this.fontStylesToName.set(schema.marks.tahoma, "Tahoma");
+ this.fontStylesToName.set(schema.marks.impact, "Impact");
+ this.fontStylesToName.set(schema.marks.crimson, "Crimson Text");
+ this.fontStyles = Array.from(this.fontStylesToName.keys());
+
+ //font sizes
+ this.fontSizeToNum = new Map();
+ this.fontSizeToNum.set(schema.marks.p10, 10);
+ this.fontSizeToNum.set(schema.marks.p12, 12);
+ this.fontSizeToNum.set(schema.marks.p14, 14);
+ this.fontSizeToNum.set(schema.marks.p16, 16);
+ this.fontSizeToNum.set(schema.marks.p24, 24);
+ this.fontSizeToNum.set(schema.marks.p32, 32);
+ this.fontSizeToNum.set(schema.marks.p48, 48);
+ this.fontSizeToNum.set(schema.marks.p72, 72);
+ this.fontSizes = Array.from(this.fontSizeToNum.keys());
+
+ //list types
+ this.listTypeToIcon = new Map();
+ this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
+ this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
+ this.listTypes = Array.from(this.listTypeToIcon.keys());
+
+ this.update(view, undefined);
+ }
+
+ //label of dropdown will change to given label
+ updateFontSizeDropdown(label: string) {
+ //filtering function - might be unecessary
+ let cut = (arr: MenuItem[]) => arr.filter(x => x);
+
+ //font SIZES
+ let fontSizeBtns: MenuItem[] = [];
+ this.fontSizeToNum.forEach((number, mark) => {
+ fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
+ });
+
+ if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); }
+ this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), {
+ label: label,
+ css: "color:white; min-width: 60px; padding-left: 5px; margin-right: 0;"
+ }) as MenuItem).render(this.view).dom;
+ this.tooltip.appendChild(this.fontSizeDom);
+ }
+
+ //label of dropdown will change to given label
+ updateFontStyleDropdown(label: string) {
+ //filtering function - might be unecessary
+ let cut = (arr: MenuItem[]) => arr.filter(x => x);
+
+ //font STYLES
+ let fontBtns: MenuItem[] = [];
+ this.fontStylesToName.forEach((name, mark) => {
+ fontBtns.push(this.dropdownMarkBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
+ });
+
+ if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); }
+ this.fontStyleDom = (new Dropdown(cut(fontBtns), {
+ label: label,
+ css: "color:white; width: 125px; margin-left: -3px; padding-left: 2px;"
+ }) as MenuItem).render(this.view).dom;
+
+ this.tooltip.appendChild(this.fontStyleDom);
+ }
+
+ updateLinkMenu() {
+ if (!this.linkEditor || !this.linkText) {
+ this.linkEditor = document.createElement("div");
+ this.linkEditor.style.color = "white";
+ this.linkText = document.createElement("div");
+ this.linkText.style.cssFloat = "left";
+ this.linkText.style.marginRight = "5px";
+ this.linkText.style.marginLeft = "5px";
+ this.linkText.setAttribute("contenteditable", "true");
+ this.linkText.style.whiteSpace = "nowrap";
+ this.linkText.style.width = "150px";
+ this.linkText.style.overflow = "hidden";
+ this.linkText.style.color = "white";
+ this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
+ let linkBtn = document.createElement("div");
+ linkBtn.textContent = ">>";
+ linkBtn.style.width = "20px";
+ linkBtn.style.height = "20px";
+ linkBtn.style.color = "white";
+ linkBtn.style.cssFloat = "left";
+ linkBtn.onpointerdown = (e: PointerEvent) => {
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = node && node.marks.find(m => m.type.name === "link");
+ if (link) {
+ let href: string = link.attrs.href;
+ if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
+ let docid = href.replace(DocServer.prepend("/doc/"), "");
+ DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+ if (f instanceof Doc) {
+ if (DocumentManager.Instance.getDocumentView(f)) {
+ DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+ }
+ else CollectionDockingView.Instance.AddRightSplit(f);
+ }
+ }));
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ };
+ this.linkDrag = document.createElement("img");
+ this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
+ this.linkDrag.style.width = "20px";
+ this.linkDrag.style.height = "20px";
+ this.linkDrag.style.color = "white";
+ this.linkDrag.style.background = "black";
+ this.linkDrag.style.cssFloat = "left";
+ this.linkDrag.onpointerdown = (e: PointerEvent) => {
+ let dragData = new DragManager.LinkDragData(this.editorProps.Document);
+ dragData.dontClearTextBox = true;
+ DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
+ {
+ handlers: {
+ dragComplete: action(() => {
+ let m = dragData.droppedDocuments;
+ this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+ }),
+ },
+ hideSource: false
+ });
+ };
+ this.linkEditor.appendChild(this.linkDrag);
+ this.linkEditor.appendChild(this.linkText);
+ this.linkEditor.appendChild(linkBtn);
+ this.tooltip.appendChild(this.linkEditor);
+ }
+
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = node && node.marks.find(m => m.type.name === "link");
+ this.linkText.textContent = link ? link.attrs.href : "-empty-";
+
+ this.linkText.onkeydown = (e: KeyboardEvent) => {
+ if (e.key === "Enter") {
+ this.makeLink(this.linkText!.textContent!);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ };
+ this.tooltip.appendChild(this.linkEditor);
+ }
+
+ makeLink = (target: string) => {
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target });
+ this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+ this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
+ node = this.view.state.selection.$from.nodeAfter;
+ link = node && node.marks.find(m => m.type.name === "link");
+ }
+
+ //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
+ updateListItemDropdown(label: string, listTypeBtn: Node) {
+ //remove old btn
+ if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
+
+ //Make a dropdown of all list types
+ let toAdd: MenuItem[] = [];
+ this.listTypeToIcon.forEach((icon, type) => {
+ toAdd.push(this.dropdownNodeBtn(icon, "width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+ });
+ //option to remove the list formatting
+ toAdd.push(this.dropdownNodeBtn("X", "width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+
+ listTypeBtn = (new Dropdown(toAdd, {
+ label: label,
+ css: "color:white; width: 40px;"
+ }) as MenuItem).render(this.view).dom;
+
+ //add this new button and return it
+ this.tooltip.appendChild(listTypeBtn);
+ return listTypeBtn;
+ }
+
+ //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text
+ changeToMarkInGroup(markType: MarkType, view: EditorView, fontMarks: MarkType[]) {
+ let { empty, $cursor, ranges } = view.state.selection as TextSelection;
+ let state = view.state;
+ let dispatch = view.dispatch;
+
+ //remove all other active font marks
+ fontMarks.forEach((type) => {
+ if (dispatch) {
+ if ($cursor) {
+ if (type.isInSet(state.storedMarks || $cursor.marks())) {
+ dispatch(state.tr.removeStoredMark(type));
+ }
+ } else {
+ let has = false, tr = state.tr;
+ for (let i = 0; !has && i < ranges.length; i++) {
+ let { $from, $to } = ranges[i];
+ has = state.doc.rangeHasMark($from.pos, $to.pos, type);
+ }
+ for (let i of ranges) {
+ let { $from, $to } = i;
+ if (has) {
+ toggleMark(type)(view.state, view.dispatch, view);
+ }
+ }
+ }
+ }
+ }); //actually apply font
+ return toggleMark(markType)(view.state, view.dispatch, view);
+ }
+
+ //remove all node typeand apply the passed-in one to the selected text
+ changeToNodeType(nodeType: NodeType | undefined, view: EditorView, allNodes: NodeType[]) {
+ //remove old
+ liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
+ if (nodeType) { //add new
+ wrapInList(nodeType)(view.state, view.dispatch);
}
- })
- })
-
- this.update(view, undefined);
- }
-
- // Helper function to create menu icons
- icon(text: string, name: string) {
- let span = document.createElement("span");
- span.className = "menuicon " + name;
- span.title = name;
- span.textContent = text;
- return span;
- }
-
- blockActive(view: EditorView) {
- const { $from, to } = view.state.selection
-
- return to <= $from.end() && $from.parent.hasMarkup(schema.nodes.bulletList);
- }
-
- //this doesn't currently work but hopefully will soon
- unorderedListIcon(): HTMLSpanElement {
- let span = document.createElement("span");
- let icon = document.createElement("FontAwesomeIcon");
- icon.className = "menuicon fa fa-smile-o";
- span.appendChild(icon);
- return span;
- }
-
- // Create an icon for a heading at the given level
- heading(level: number) {
- return {
- command: setBlockType(schema.nodes.heading, { level }),
- dom: this.icon("H" + level, "heading")
- }
- }
-
- //updates the tooltip menu when the selection changes
- update(view: EditorView, lastState: EditorState | undefined) {
- let state = view.state
- // Don't do anything if the document/selection didn't change
- if (lastState && lastState.doc.eq(state.doc) &&
- lastState.selection.eq(state.selection)) return
-
- // Hide the tooltip if the selection is empty
- if (state.selection.empty) {
- this.tooltip.style.display = "none"
- return
- }
-
- // Otherwise, reposition it and update its content
- this.tooltip.style.display = ""
- let { from, to } = state.selection
- // These are in screen coordinates
- //check this - tranform
- let start = view.coordsAtPos(from), end = view.coordsAtPos(to)
- // The box in which the tooltip is positioned, to use as base
- let box = this.tooltip.offsetParent!.getBoundingClientRect()
- // Find a center-ish x position from the selection endpoints (when
- // crossing lines, end may be more to the left)
- let left = Math.max((start.left + end.left) / 2, start.left + 3)
- this.tooltip.style.left = (left - box.left) + "px"
- let width = Math.abs(start.left - end.left) / 2;
- let mid = Math.min(start.left, end.left) + width;
- //THIS WIDTH IS 15 * NUMBER OF ICONS + 15
- this.tooltip.style.width = 120 + "px";
- this.tooltip.style.bottom = (box.bottom - start.top) + "px";
- }
-
- destroy() { this.tooltip.remove() }
-} \ No newline at end of file
+ }
+
+ //makes a button for the drop down FOR MARKS
+ //css is the style you want applied to the button
+ dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType<any>, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) {
+ return new MenuItem({
+ title: "",
+ label: label,
+ execEvent: "",
+ class: "menuicon",
+ css: css,
+ enable(state) { return true; },
+ run() {
+ changeToMarkInGroup(markType, view, groupMarks);
+ }
+ });
+ }
+
+ //makes a button for the drop down FOR NODE TYPES
+ //css is the style you want applied to the button
+ dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
+ return new MenuItem({
+ title: "",
+ label: label,
+ execEvent: "",
+ class: "menuicon",
+ css: css,
+ enable(state) { return true; },
+ run() {
+ changeToNodeInGroup(nodeType, view, groupNodes);
+ }
+ });
+ }
+
+ // Helper function to create menu icons
+ icon(text: string, name: string) {
+ let span = document.createElement("span");
+ span.className = name + " menuicon";
+ span.title = name;
+ span.textContent = text;
+ span.style.color = "white";
+ return span;
+ }
+
+ //method for checking whether node can be inserted
+ canInsert(state: EditorState, nodeType: NodeType<Schema<string, string>>) {
+ let $from = state.selection.$from;
+ for (let d = $from.depth; d >= 0; d--) {
+ let index = $from.index(d);
+ if ($from.node(d).canReplaceWith(index, index, nodeType)) return true;
+ }
+ return false;
+ }
+
+
+ //adapted this method - use it to check if block has a tag (ie bulleting)
+ blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
+ let attrs = {};
+
+ if (state.selection instanceof NodeSelection) {
+ const sel: NodeSelection = state.selection;
+ let $from = sel.$from;
+ let to = sel.to;
+ let node = sel.node;
+
+ if (node) {
+ return node.hasMarkup(type, attrs);
+ }
+
+ return to <= $from.end() && $from.parent.hasMarkup(type, attrs);
+ }
+ }
+
+ // Create an icon for a heading at the given level
+ heading(level: number) {
+ return {
+ command: setBlockType(schema.nodes.heading, { level }),
+ dom: this.icon("H" + level, "heading")
+ };
+ }
+
+ //updates the tooltip menu when the selection changes
+ update(view: EditorView, lastState: EditorState | undefined) {
+ let state = view.state;
+ // Don't do anything if the document/selection didn't change
+ if (lastState && lastState.doc.eq(state.doc) &&
+ lastState.selection.eq(state.selection)) return;
+
+ // Hide the tooltip if the selection is empty
+ if (state.selection.empty) {
+ this.tooltip.style.display = "none";
+ return;
+ }
+
+ // Otherwise, reposition it and update its content
+ this.tooltip.style.display = "";
+ let { from, to } = state.selection;
+ let start = view.coordsAtPos(from), end = view.coordsAtPos(to);
+ // The box in which the tooltip is positioned, to use as base
+ let box = this.tooltip.offsetParent!.getBoundingClientRect();
+ // Find a center-ish x position from the selection endpoints (when
+ // crossing lines, end may be more to the left)
+ let left = Math.max((start.left + end.left) / 2, start.left + 3);
+ this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
+ let mid = Math.min(start.left, end.left) + width;
+
+ this.tooltip.style.width = 225 + "px";
+ this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+
+ //UPDATE LIST ITEM DROPDOWN
+ this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
+
+ //UPDATE FONT STYLE DROPDOWN
+ let activeStyles = this.activeMarksOnSelection(this.fontStyles);
+ if (activeStyles.length === 1) {
+ // if we want to update something somewhere with active font name
+ let fontName = this.fontStylesToName.get(activeStyles[0]);
+ if (fontName) { this.updateFontStyleDropdown(fontName); }
+ } else if (activeStyles.length === 0) {
+ //crimson on default
+ this.updateFontStyleDropdown("Crimson Text");
+ } else {
+ this.updateFontStyleDropdown("Various");
+ }
+
+ //UPDATE FONT SIZE DROPDOWN
+ let activeSizes = this.activeMarksOnSelection(this.fontSizes);
+ if (activeSizes.length === 1) { //if there's only one active font size
+ let size = this.fontSizeToNum.get(activeSizes[0]);
+ if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+ } else if (activeSizes.length === 0) {
+ //should be 14 on default
+ this.updateFontSizeDropdown("14 pt");
+ } else { //multiple font sizes selected
+ this.updateFontSizeDropdown("Various");
+ }
+
+ this.updateLinkMenu();
+ }
+
+ //finds all active marks on selection in given group
+ activeMarksOnSelection(markGroup: MarkType[]) {
+ //current selection
+ let { empty, $cursor, ranges } = this.view.state.selection as TextSelection;
+ let state = this.view.state;
+ let dispatch = this.view.dispatch;
+
+ let activeMarks = markGroup.filter(mark => {
+ if (dispatch) {
+ let has = false, tr = state.tr;
+ for (let i = 0; !has && i < ranges.length; i++) {
+ let { $from, $to } = ranges[i];
+ return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+ }
+ }
+ return false;
+ });
+ return activeMarks;
+ }
+
+ destroy() { this.tooltip.remove(); }
+}
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index 3e1039166..e9170ec36 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -3,7 +3,7 @@ export class Transform {
private _translateY: number = 0;
private _scale: number = 1;
- static get Identity(): Transform {
+ static Identity(): Transform {
return new Transform(0, 0, 1);
}
@@ -17,78 +17,64 @@ export class Transform {
this._scale = scale;
}
- translate = (x: number, y: number): Transform => {
+ translate = (x: number, y: number): this => {
this._translateX += x;
this._translateY += y;
return this;
}
- scale = (scale: number): Transform => {
+ scale = (scale: number): this => {
this._scale *= scale;
this._translateX *= scale;
this._translateY *= scale;
return this;
}
- scaleAbout = (scale: number, x: number, y: number): Transform => {
+ scaleAbout = (scale: number, x: number, y: number): this => {
this._translateX += x * this._scale - x * this._scale * scale;
this._translateY += y * this._scale - y * this._scale * scale;
this._scale *= scale;
return this;
}
- transform = (transform: Transform): Transform => {
+ transform = (transform: Transform): this => {
this._translateX = transform._translateX + transform._scale * this._translateX;
this._translateY = transform._translateY + transform._scale * this._translateY;
this._scale *= transform._scale;
return this;
}
- preTranslate = (x: number, y: number): Transform => {
+ preTranslate = (x: number, y: number): this => {
this._translateX += this._scale * x;
this._translateY += this._scale * y;
return this;
}
- preScale = (scale: number): Transform => {
+ preScale = (scale: number): this => {
this._scale *= scale;
return this;
}
- preTransform = (transform: Transform): Transform => {
+ preTransform = (transform: Transform): this => {
this._translateX += transform._translateX * this._scale;
this._translateY += transform._translateY * this._scale;
this._scale *= transform._scale;
return this;
}
- translated = (x: number, y: number): Transform => {
- return this.copy().translate(x, y);
- }
+ translated = (x: number, y: number): Transform => this.copy().translate(x, y);
- preTranslated = (x: number, y: number): Transform => {
- return this.copy().preTranslate(x, y);
- }
+ preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y);
- scaled = (scale: number): Transform => {
- return this.copy().scale(scale);
- }
+ scaled = (scale: number): Transform => this.copy().scale(scale);
- scaledAbout = (scale: number, x: number, y: number): Transform => {
- return this.copy().scaleAbout(scale, x, y);
- }
+ scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y);
- preScaled = (scale: number): Transform => {
- return this.copy().preScale(scale);
- }
+ preScaled = (scale: number): Transform => this.copy().preScale(scale);
- transformed = (transform: Transform): Transform => {
- return this.copy().transform(transform);
- }
+ transformed = (transform: Transform): Transform => this.copy().transform(transform);
- preTransformed = (transform: Transform): Transform => {
- return this.copy().preTransform(transform);
- }
+ preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
transformPoint = (x: number, y: number): [number, number] => {
x *= this._scale;
@@ -98,9 +84,7 @@ export class Transform {
return [x, y];
}
- transformDirection = (x: number, y: number): [number, number] => {
- return [x * this._scale, y * this._scale];
- }
+ transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } {
[x, y] = this.transformPoint(x, y);
@@ -108,12 +92,8 @@ export class Transform {
return { x, y, width, height };
}
- inverse = () => {
- return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale)
- }
+ inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale);
- copy = () => {
- return new Transform(this._translateX, this._translateY, this._scale);
- }
+ copy = () => new Transform(this._translateX, this._translateY, this._scale);
} \ No newline at end of file
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 0714a7f5c..532ba78eb 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -36,7 +36,5 @@ export class TypedEvent<T> {
this.listenersOncer = [];
}
- pipe = (te: TypedEvent<T>): Disposable => {
- return this.on((e) => te.emit(e));
- }
+ pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
} \ No newline at end of file
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 46ad558f3..c0ed015bd 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,4 +1,14 @@
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
+import 'source-map-support/register';
+import { Without } from "../../Utils";
+
+function getBatchName(target: any, key: string | symbol): string {
+ let keyName = key.toString();
+ if (target && target.constructor && target.constructor.name) {
+ return `${target.constructor.name}.${keyName}`;
+ }
+ return keyName;
+}
function propertyDecorator(target: any, key: string | symbol) {
Object.defineProperty(target, key, {
@@ -13,18 +23,31 @@ function propertyDecorator(target: any, key: string | symbol) {
writable: true,
configurable: true,
value: function (...args: any[]) {
+ let batch = UndoManager.StartBatch(getBatchName(target, key));
try {
- UndoManager.StartBatch();
return value.apply(this, args);
} finally {
- UndoManager.EndBatch();
+ batch.end();
}
}
- })
+ });
}
- })
+ });
}
-export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
+
+export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
+export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
+export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
+ if (!key) {
+ return function () {
+ let batch = UndoManager.StartBatch("");
+ try {
+ return target.apply(undefined, arguments);
+ } finally {
+ batch.end();
+ }
+ };
+ }
if (!descriptor) {
propertyDecorator(target, key);
return;
@@ -32,13 +55,13 @@ export function undoBatch(target: any, key: string | symbol, descriptor?: TypedP
const oldFunction = descriptor.value;
descriptor.value = function (...args: any[]) {
+ let batch = UndoManager.StartBatch(getBatchName(target, key));
try {
- UndoManager.StartBatch()
- return oldFunction.apply(this, args)
+ return oldFunction.apply(this, args);
} finally {
- UndoManager.EndBatch()
+ batch.end();
}
- }
+ };
return descriptor;
}
@@ -70,26 +93,64 @@ export namespace UndoManager {
return redoStack.length > 0;
}
- export function StartBatch(): void {
+ export function PrintBatches(): void {
+ GetOpenBatches().forEach(batch => console.log(batch.batchName));
+ }
+
+ let openBatches: Batch[] = [];
+ export function GetOpenBatches(): Without<Batch, 'end'>[] {
+ return openBatches;
+ }
+ export function TraceOpenBatches() {
+ console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join("\n\t")}\n`);
+ }
+ export class Batch {
+ private disposed: boolean = false;
+
+ constructor(readonly batchName: string) {
+ openBatches.push(this);
+ }
+
+ private dispose = (cancel: boolean) => {
+ if (this.disposed) {
+ throw new Error("Cannot dispose an already disposed batch");
+ }
+ this.disposed = true;
+ openBatches.splice(openBatches.indexOf(this));
+ EndBatch(cancel);
+ }
+
+ end = () => { this.dispose(false); };
+ cancel = () => { this.dispose(true); };
+ }
+
+ export function StartBatch(batchName: string): Batch {
batchCounter++;
if (batchCounter > 0) {
currentBatch = [];
}
+ return new Batch(batchName);
}
- export const EndBatch = action(() => {
+ const EndBatch = action((cancel: boolean = false) => {
batchCounter--;
if (batchCounter === 0 && currentBatch && currentBatch.length) {
- undoStack.push(currentBatch);
+ if (!cancel) {
+ undoStack.push(currentBatch);
+ }
redoStack.length = 0;
currentBatch = undefined;
}
- })
+ });
- export function RunInBatch(fn: () => void) {
- StartBatch();
- fn();
- EndBatch();
+ //TODO Make this return the return value
+ export function RunInBatch<T>(fn: () => T, batchName: string) {
+ let batch = StartBatch(batchName);
+ try {
+ return runInAction(fn);
+ } finally {
+ batch.end();
+ }
}
export const Undo = action(() => {
@@ -109,7 +170,7 @@ export namespace UndoManager {
undoing = false;
redoStack.push(commands);
- })
+ });
export const Redo = action(() => {
if (redoStack.length === 0) {
@@ -122,12 +183,12 @@ export namespace UndoManager {
}
undoing = true;
- for (let i = 0; i < commands.length; i++) {
- commands[i].redo();
+ for (const command of commands) {
+ command.redo();
}
undoing = false;
undoStack.push(commands);
- })
+ });
} \ No newline at end of file
diff --git a/src/client/util/jsx-decl.d.ts b/src/client/util/jsx-decl.d.ts
new file mode 100644
index 000000000..532f06178
--- /dev/null
+++ b/src/client/util/jsx-decl.d.ts
@@ -0,0 +1 @@
+declare module 'react-jsx-parser';
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 679f73f42..47c3481b2 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -174,13 +174,14 @@ declare class ListField<T> extends BasicField<T[]>{
Copy(): Field;
}
declare class Key extends Field {
+ constructor(name:string);
Name: string;
TrySetValue(value: any): boolean;
GetValue(): any;
Copy(): Field;
ToScriptString(): string;
}
-declare type FIELD_WAITING = "<Waiting>";
+declare type FIELD_WAITING = null;
declare type Opt<T> = T | undefined;
declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
// @ts-ignore
@@ -213,3 +214,12 @@ declare class Document extends Field {
GetAllPrototypes(): Document[];
MakeDelegate(): Document;
}
+
+declare const KeyStore: {
+ [name: string]: Key;
+}
+
+// @ts-ignore
+declare const console: any;
+
+declare const Documents: any;
diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store
index 6bd614c8b..5008ddfcf 100644
--- a/src/client/views/.DS_Store
+++ b/src/client/views/.DS_Store
Binary files differ
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 8bc7708f7..3b1b18f88 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,9 +1,24 @@
+@import "globalCssVariables";
.contextMenu-cont {
- position: absolute;
- display: flex;
- z-index: 1000;
- box-shadow: #AAAAAA .2vw .2vw .4vw;
- flex-direction: column;
+ position: absolute;
+ display: flex;
+ z-index: $contextMenu-zindex;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
+ flex-direction: column;
+}
+
+.contextMenu-item:first-child {
+ background: $intermediate-color;
+ color: $light-color;
+}
+
+.contextMenu-item:first-child::placeholder {
+ color: $light-color;
+}
+
+.contextMenu-item:first-child:hover {
+ background: $intermediate-color;
+ color: $light-color;
}
.subMenu-cont {
@@ -30,7 +45,7 @@
transition: all .1s;
border-width: .11px;
border-style: none;
- border-color: rgb(187, 186, 186);
+ border-color: $intermediate-color; // rgb(187, 186, 186);
border-bottom-style: solid;
padding: 10px;
white-space: nowrap;
@@ -38,8 +53,8 @@
}
.contextMenu-item:hover {
- transition: all .1s;
- background: #B0E0E6;
+ transition: all 0.1s;
+ background: $lighter-alt-accent;
}
.contextMenu-description {
@@ -51,4 +66,4 @@
.icon-background {
background-color: #DDDDDD;
-} \ No newline at end of file
+}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 39ccb55b0..30ac23adf 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -11,13 +11,15 @@ library.add(faSearch);
@observer
export class ContextMenu extends React.Component {
- static Instance: ContextMenu
+ static Instance: ContextMenu;
@observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault(), icon: "smile" }];
@observable private _pageX: number = 0;
@observable private _pageY: number = 0;
@observable private _display: string = "none";
@observable private _searchString: string = "";
+ // afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
+ @observable private _yRelativeToTop: boolean = true;
private ref: React.RefObject<HTMLDivElement>;
@@ -25,15 +27,15 @@ export class ContextMenu extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
- this.ref = React.createRef()
+ this.ref = React.createRef();
ContextMenu.Instance = this;
}
@action
clearItems() {
- this._items = []
- this._display = "none"
+ this._items = [];
+ this._display = "none";
}
@action
@@ -50,18 +52,28 @@ export class ContextMenu extends React.Component {
@action
displayMenu(x: number, y: number) {
- this._pageX = x
- this._pageY = y
+ //maxX and maxY will change if the UI/font size changes, but will work for any amount
+ //of items added to the menu
+ let maxX = window.innerWidth - 150;
+ let maxY = window.innerHeight - ((this._items.length + 1/*for search box*/) * 34 + 30);
+
+ this._pageX = x > maxX ? maxX : x;
+ this._pageY = y > maxY ? maxY : y;
this._searchString = "";
- this._display = "flex"
+ this._display = "flex";
}
intersects = (x: number, y: number): boolean => {
if (this.ref.current && this._display !== "none") {
- if (x >= this._pageX && x <= this._pageX + this.ref.current.getBoundingClientRect().width) {
- if (y >= this._pageY && y <= this._pageY + this.ref.current.getBoundingClientRect().height) {
+ let menuSize = { width: this.ref.current.getBoundingClientRect().width, height: this.ref.current.getBoundingClientRect().height };
+
+ let upperLeft = { x: this._pageX, y: this._yRelativeToTop ? this._pageY : window.innerHeight - (this._pageY + menuSize.height) };
+ let bottomRight = { x: this._pageX + menuSize.width, y: this._yRelativeToTop ? this._pageY + menuSize.height : window.innerHeight - this._pageY };
+
+ if (x >= upperLeft.x && x <= bottomRight.x) {
+ if (y >= upperLeft.y && y <= bottomRight.y) {
return true;
}
}
@@ -70,22 +82,23 @@ export class ContextMenu extends React.Component {
}
render() {
+ let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } :
+ { left: this._pageX, bottom: this._pageY, display: this._display };
+
+
return (
- <div className="contextMenu-cont" style={{ left: this._pageX, top: this._pageY, display: this._display }} ref={this.ref}>
+ <div className="contextMenu-cont" style={style} ref={this.ref}>
<span>
<span className="icon-background">
<FontAwesomeIcon icon="circle" size="lg" />
<FontAwesomeIcon icon="search" size="lg" />
</span>
- <input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} ></input>
+ <input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} />
</span>
- {this._items.filter(prop => {
- return prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1;
- }).map(prop => {
- return <ContextMenuItem {...prop} key={prop.description} />
- })}
+ {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
+ map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
</div>
- )
+ );
}
@action
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index fac7342aa..aefc633bc 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -15,6 +15,9 @@ export interface SubmenuProps {
subitems: ContextMenuProps[];
}
+export interface ContextMenuItemProps {
+ type: ContextMenuProps | SubmenuProps;
+}
export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
@observer
@@ -22,7 +25,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
@observable private _items: Array<ContextMenuProps> = [];
@observable private overItem = false;
- constructor(props: ContextMenuProps) {
+ constructor(props: ContextMenuProps | SubmenuProps) {
super(props);
if ("subitems" in this.props) {
this.props.subitems.forEach(i => this._items.push(i));
@@ -50,7 +53,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
</div>)
}
return (
- <div className="contextMenu-item" onMouseEnter={action(() => {
+ <div className="contextMenu-item" onClick={this.props.event} onMouseEnter={action(() => {
this.overItem = true
})} onMouseLeave={action(() => this.overItem = false)}>
<div className="contextMenu-description"> {this.props.description}</div>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
new file mode 100644
index 000000000..d6562492f
--- /dev/null
+++ b/src/client/views/DocComponent.tsx
@@ -0,0 +1,14 @@
+import * as React from 'react';
+import { Doc } from '../../new_fields/Doc';
+import { computed } from 'mobx';
+
+export function DocComponent<P extends { Document: Doc }, T>(schemaCtor: (doc: Doc) => T) {
+ class Component extends React.Component<P> {
+ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
+ @computed
+ get Document(): T {
+ return schemaCtor(this.props.Document);
+ }
+ }
+ return Component;
+} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index fb9091dfc..ba9f32d7d 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,51 +1,226 @@
-#documentDecorations-container {
+@import "globalCssVariables";
+
+$linkGap : 3px;
+.documentDecorations {
position: absolute;
+}
+
+.documentDecorations-container {
+ z-index: $docDecorations-zindex;
+ position: absolute;
+ top: 0;
+ left: 0;
display: grid;
- z-index: 1000;
- grid-template-rows: 20px 1fr 20px 0px;
- grid-template-columns: 20px 1fr 20px;
+ grid-template-rows: 20px 8px 1fr 8px;
+ grid-template-columns: 8px 16px 1fr 8px 8px;
pointer-events: none;
+
#documentDecorations-centerCont {
+ grid-column: 3;
background: none;
}
+
.documentDecorations-resizer {
pointer-events: auto;
- background: lightblue;
- opacity: 0.4;
+ background: $alt-accent;
+ opacity: 0.8;
}
+
+ #documentDecorations-topLeftResizer,
+ #documentDecorations-leftResizer,
+ #documentDecorations-bottomLeftResizer {
+ grid-column: 1
+ }
+
+ #documentDecorations-topResizer,
+ #documentDecorations-bottomResizer {
+ grid-column-start: 2;
+ grid-column-end: 5;
+ }
+
+ #documentDecorations-bottomRightResizer,
+ #documentDecorations-topRightResizer,
+ #documentDecorations-rightResizer {
+ grid-column-start: 5;
+ grid-column-end: 7;
+ }
+
#documentDecorations-topLeftResizer,
#documentDecorations-bottomRightResizer {
cursor: nwse-resize;
}
+
#documentDecorations-topRightResizer,
#documentDecorations-bottomLeftResizer {
cursor: nesw-resize;
}
+
#documentDecorations-topResizer,
#documentDecorations-bottomResizer {
cursor: ns-resize;
}
+
#documentDecorations-leftResizer,
#documentDecorations-rightResizer {
cursor: ew-resize;
}
-
+ .title{
+ background: $alt-accent;
+ grid-column-start: 3;
+ grid-column-end: 4;
+ pointer-events: auto;
+ overflow: hidden;
+ }
+}
+
+
+.documentDecorations-closeButton {
+ background: $alt-accent;
+ opacity: 0.8;
+ grid-column-start: 4;
+ grid-column-end: 6;
+ pointer-events: all;
+ text-align: center;
+ cursor: pointer;
+}
+
+.documentDecorations-minimizeButton {
+ background: $alt-accent;
+ opacity: 0.8;
+ grid-column-start: 1;
+ grid-column-end: 3;
+ pointer-events: all;
+ text-align: center;
+ cursor: pointer;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: $MINIMIZED_ICON_SIZE;
+ height: $MINIMIZED_ICON_SIZE;
+}
+
+.documentDecorations-background {
+ background: lightblue;
+ position: absolute;
+ opacity: 0.1;
}
-.linkButton-empty {
+
+.linkFlyout {
+ grid-column: 2/4;
+ margin-top: $linkGap;
+}
+
+.linkButton-empty:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
+.linkButton-nonempty:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
+.link-button-container {
+ grid-column: 1/4;
+ width: auto;
+ height: auto;
+ display: flex;
+ flex-direction: row;
+}
+
+.linkButton-linker {
+ margin-left: 5px;
+ margin-top: $linkGap;
height: 20px;
width: 20px;
- margin-top: 10px;
+ text-align: center;
border-radius: 50%;
- opacity: 0.6;
pointer-events: auto;
- background-color: #2B6091;
+ color: $dark-color;
+ border: $dark-color 1px solid;
+}
+
+.linkButton-linker:hover {
+ cursor: pointer;
+ transform: scale(1.05);
}
-.linkButton-nonempty {
+
+.linkButton-empty, .linkButton-nonempty {
height: 20px;
width: 20px;
- margin-top: 10px;
border-radius: 50%;
- opacity: 0.6;
+ opacity: 0.9;
pointer-events: auto;
- background-color: rgb(35, 165, 42);
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+}
+
+.templating-menu {
+ position: absolute;
+ bottom: 0;
+ left: 50px;
+ pointer-events: auto;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.fa-icon-link {
+ margin-top: 3px;
+}
+.templating-button {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ opacity: 0.9;
+ font-size:14;
+ background-color: $dark-color;
+ color: $light-color;
+ text-align: center;
+ cursor: pointer;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ }
+}
+
+#template-list {
+ position: absolute;
+ top: 0;
+ left: 30px;
+ width: max-content;
+ font-family: $sans-serif;
+ font-size: 12px;
+ background-color: $light-color-secondary;
+ padding: 2px 12px;
+ list-style: none;
+ .templateToggle {
+ text-align: left;
+ }
+
+ input {
+ margin-right: 10px;
+ }
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dc62f97cf..7083b1003 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,30 +1,121 @@
-import { observable, computed, action } from "mobx";
-import React = require("react");
-import { SelectionManager } from "../util/SelectionManager";
+import { action, computed, observable, runInAction, untracked, reaction } from "mobx";
import { observer } from "mobx-react";
-import './DocumentDecorations.scss'
-import { KeyStore } from '../../fields/KeyStore'
-import { NumberField } from "../../fields/NumberField";
-import { props } from "bluebird";
-import { DragManager } from "../util/DragManager";
+import { emptyFunction, Utils } from "../../Utils";
+import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
+import { SelectionManager } from "../util/SelectionManager";
+import { undoBatch } from "../util/UndoManager";
+import './DocumentDecorations.scss';
+import { DocumentView, PositionDocument } from "./nodes/DocumentView";
import { LinkMenu } from "./nodes/LinkMenu";
-import { ListField } from "../../fields/ListField";
+import { TemplateMenu } from "./TemplateMenu";
+import React = require("react");
+import { Template, Templates } from "./Templates";
+import { CompileScript } from "../util/Scripting";
+import { IconBox } from "./nodes/IconBox";
+import { Cast, FieldValue, NumCast, StrCast } from "../../new_fields/Types";
+import { Doc, FieldResult } from "../../new_fields/Doc";
+import { listSpec } from "../../new_fields/Schema";
+import { Docs } from "../documents/Documents";
+import { List } from "../../new_fields/List";
const higflyout = require("@hig/flyout");
-const { anchorPoints } = higflyout;
-const Flyout = higflyout.default;
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+import { faLink } from '@fortawesome/free-solid-svg-icons';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
+import { CollectionView } from "./collections/CollectionView";
+import { DocumentManager } from "../util/DocumentManager";
+import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { FieldView } from "./nodes/FieldView";
+
+library.add(faLink);
@observer
-export class DocumentDecorations extends React.Component {
- static Instance: DocumentDecorations
- private _resizer = ""
+export class DocumentDecorations extends React.Component<{}, { value: string }> {
+ static Instance: DocumentDecorations;
private _isPointerDown = false;
+ private _resizing = "";
+ private keyinput: React.RefObject<HTMLInputElement>;
+ private _resizeBorderWidth = 16;
+ private _linkBoxHeight = 20 + 3; // link button height + margin
+ private _titleHeight = 20;
private _linkButton = React.createRef<HTMLDivElement>();
+ private _linkerButton = React.createRef<HTMLDivElement>();
+ private _downX = 0;
+ private _downY = 0;
+ private _iconDoc?: Doc = undefined;
+ @observable private _minimizedX = 0;
+ @observable private _minimizedY = 0;
+ @observable private _title: string = "";
+ @observable private _edtingTitle = false;
+ @observable private _fieldKey = "title";
@observable private _hidden = false;
+ @observable private _opacity = 1;
+ @observable private _removeIcon = false;
+ @observable public Interacting = false;
constructor(props: Readonly<{}>) {
- super(props)
+ super(props);
+ DocumentDecorations.Instance = this;
+ this.keyinput = React.createRef();
+ reaction(() => SelectionManager.SelectedDocuments().slice(), docs => this._edtingTitle = false);
+ }
- DocumentDecorations.Instance = this
+ @action titleChanged = (event: any) => { this._title = event.target.value; };
+ @action titleBlur = () => { this._edtingTitle = false; };
+ @action titleEntered = (e: any) => {
+ var key = e.keyCode || e.which;
+ // enter pressed
+ if (key === 13) {
+ var text = e.target.value;
+ if (text[0] === '#') {
+ this._fieldKey = text.slice(1, text.length);
+ this._title = this.selectionTitle;
+ }
+ else {
+ if (SelectionManager.SelectedDocuments().length > 0) {
+ let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
+ if (typeof field === "number") {
+ SelectionManager.SelectedDocuments().forEach(d => {
+ let doc = d.props.Document.proto ? d.props.Document.proto : d.props.Document;
+ doc[this._fieldKey] = +this._title;
+ });
+ } else {
+ SelectionManager.SelectedDocuments().forEach(d => {
+ let doc = d.props.Document.proto ? d.props.Document.proto : d.props.Document;
+ doc[this._fieldKey] = this._title;
+ });
+ }
+ }
+ }
+ e.target.blur();
+ }
+ }
+ @action onTitleDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onTitleMove);
+ document.removeEventListener("pointerup", this.onTitleUp);
+ document.addEventListener("pointermove", this.onTitleMove);
+ document.addEventListener("pointerup", this.onTitleUp);
+ }
+ @action onTitleMove = (e: PointerEvent): void => {
+ if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
+ this.Interacting = true;
+ }
+ if (this.Interacting) this.onBackgroundMove(e);
+ e.stopPropagation();
+ }
+ @action onTitleUp = (e: PointerEvent): void => {
+ if (Math.abs(e.clientX - this._downX) < 4 || Math.abs(e.clientY - this._downY) < 4) {
+ this._title = this.selectionTitle;
+ this._edtingTitle = true;
+ }
+ document.removeEventListener("pointermove", this.onTitleMove);
+ document.removeEventListener("pointerup", this.onTitleUp);
+ this.onBackgroundUp(e);
}
@computed
@@ -39,20 +130,192 @@ export class DocumentDecorations extends React.Component {
return {
x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
- }
+ };
}, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
}
+ onBackgroundDown = (e: React.PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onBackgroundMove);
+ document.removeEventListener("pointerup", this.onBackgroundUp);
+ document.addEventListener("pointermove", this.onBackgroundMove);
+ document.addEventListener("pointerup", this.onBackgroundUp);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ @action
+ onBackgroundMove = (e: PointerEvent): void => {
+ let dragDocView = SelectionManager.SelectedDocuments()[0];
+ const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
+ const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);
+ let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
+ dragData.xOffset = xoff;
+ dragData.yOffset = yoff;
+ dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;
+ this.Interacting = true;
+ this._hidden = true;
+ document.removeEventListener("pointermove", this.onBackgroundMove);
+ document.removeEventListener("pointerup", this.onBackgroundUp);
+ document.removeEventListener("pointermove", this.onTitleMove);
+ document.removeEventListener("pointerup", this.onTitleUp);
+ DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
+ handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
+ hideSource: true
+ });
+ e.stopPropagation();
+ }
- @computed
- public get Hidden() { return this._hidden; }
- public set Hidden(value: boolean) { this._hidden = value; }
+ @action
+ onBackgroundUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onBackgroundMove);
+ document.removeEventListener("pointerup", this.onBackgroundUp);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ onCloseDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ document.removeEventListener("pointermove", this.onCloseMove);
+ document.addEventListener("pointermove", this.onCloseMove);
+ document.removeEventListener("pointerup", this.onCloseUp);
+ document.addEventListener("pointerup", this.onCloseUp);
+ }
+ }
+ onCloseMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ }
+ }
+ @undoBatch
+ @action
+ onCloseUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ SelectionManager.SelectedDocuments().map(dv => dv.props.removeDocument && dv.props.removeDocument(dv.props.Document));
+ SelectionManager.DeselectAll();
+ document.removeEventListener("pointermove", this.onCloseMove);
+ document.removeEventListener("pointerup", this.onCloseUp);
+ }
+ }
+ @action
+ onMinimizeDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ this._iconDoc = undefined;
+ if (e.button === 0) {
+ this._downX = e.pageX;
+ this._downY = e.pageY;
+ this._removeIcon = false;
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+ this._minimizedX = selDocPos[0] + 12;
+ this._minimizedY = selDocPos[1] + 12;
+ document.removeEventListener("pointermove", this.onMinimizeMove);
+ document.addEventListener("pointermove", this.onMinimizeMove);
+ document.removeEventListener("pointerup", this.onMinimizeUp);
+ document.addEventListener("pointerup", this.onMinimizeUp);
+ }
+ }
+
+ @action
+ onMinimizeMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (Math.abs(e.pageX - this._downX) > Utils.DRAG_THRESHOLD ||
+ Math.abs(e.pageY - this._downY) > Utils.DRAG_THRESHOLD) {
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+ let snapped = Math.abs(e.pageX - selDocPos[0]) < 20 && Math.abs(e.pageY - selDocPos[1]) < 20;
+ this._minimizedX = snapped ? selDocPos[0] + 4 : e.clientX;
+ this._minimizedY = snapped ? selDocPos[1] - 18 : e.clientY;
+ let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+
+ if (selectedDocs.length > 1) {
+ this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString());
+ this.moveIconDoc(this._iconDoc);
+ } else {
+ this.getIconDoc(selectedDocs[0]).then(icon => icon && this.moveIconDoc(this._iconDoc = icon));
+ }
+ this._removeIcon = snapped;
+ }
+ }
+ @undoBatch
+ @action
+ onMinimizeUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ document.removeEventListener("pointermove", this.onMinimizeMove);
+ document.removeEventListener("pointerup", this.onMinimizeUp);
+ let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+ if (this._iconDoc && selectedDocs.length === 1 && this._removeIcon) {
+ selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
+ }
+ if (!this._removeIcon) {
+ if (selectedDocs.length === 1) {
+ this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
+ } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
+ let docViews = SelectionManager.ViewsSortedVertically();
+ let topDocView = docViews[0];
+ let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
+ if (ind !== -1) {
+ topDocView.templates.splice(ind, 1);
+ topDocView.props.Document.subBulletDocs = undefined;
+ } else {
+ topDocView.addTemplate(Templates.Bullet);
+ topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
+ }
+ }
+ }
+ this._removeIcon = false;
+ }
+ runInAction(() => this._minimizedX = this._minimizedY = 0);
+ }
+
+ @undoBatch
+ @action createIcon = (selected: DocumentView[], layoutString: string): Doc => {
+ let doc = selected[0].props.Document;
+ let iconDoc = Docs.IconDocument(layoutString);
+ iconDoc.isButton = true;
+ iconDoc.proto!.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
+ iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
+ iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
+ iconDoc.isMinimized = false;
+ iconDoc.width = Number(MINIMIZED_ICON_SIZE);
+ iconDoc.height = Number(MINIMIZED_ICON_SIZE);
+ iconDoc.x = NumCast(doc.x);
+ iconDoc.y = NumCast(doc.y) - 24;
+ iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document.proto!));
+ doc.minimizedDoc = iconDoc;
+ selected[0].props.addDocument && selected[0].props.addDocument(iconDoc, false);
+ return iconDoc;
+ }
+ @action
+ public getIconDoc = async (docView: DocumentView): Promise<Doc | undefined> => {
+ let doc = docView.props.Document;
+ let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc);
+
+ if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) {
+ const layout = StrCast(doc.backgroundLayout, StrCast(doc.layout, FieldView.LayoutString(DocumentView)));
+ iconDoc = this.createIcon([docView], layout);
+ }
+ return iconDoc;
+ }
+ moveIconDoc(iconDoc: Doc) {
+ let selView = SelectionManager.SelectedDocuments()[0];
+ let zoom = NumCast(selView.props.Document.zoomBasis, 1);
+ let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()).scale(1 / zoom).
+ transformPoint(this._minimizedX - 12, this._minimizedY - 12);
+ iconDoc.x = where[0] + NumCast(selView.props.Document.x);
+ iconDoc.y = where[1] + NumCast(selView.props.Document.y);
+ }
+
+ @action
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
this._isPointerDown = true;
- this._resizer = e.currentTarget.id;
+ this._resizing = e.currentTarget.id;
+ this.Interacting = true;
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -60,46 +323,62 @@ export class DocumentDecorations extends React.Component {
}
}
- onLinkButtonDown = (e: React.PointerEvent): void => {
- // if ()
- // let linkMenu = new LinkMenu(SelectionManager.SelectedDocuments()[0]);
- // linkMenu.Hidden = false;
- console.log("down");
+ onLinkerButtonDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+ document.addEventListener("pointermove", this.onLinkerButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkerButtonUp);
+ document.addEventListener("pointerup", this.onLinkerButtonUp);
+ }
+ onLinkerButtonUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkerButtonUp);
+ e.stopPropagation();
+ }
+ @action
+ onLinkerButtonMoved = (e: PointerEvent): void => {
+ if (this._linkerButton.current !== null) {
+ document.removeEventListener("pointermove", this.onLinkerButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkerButtonUp);
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let container = selDoc.props.ContainingCollectionView ? selDoc.props.ContainingCollectionView.props.Document.proto : undefined;
+ let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
+ FormattedTextBox.InputBoxOverlay = undefined;
+ DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
e.stopPropagation();
- document.removeEventListener("pointermove", this.onLinkButtonMoved)
+ }
+
+ onLinkButtonDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.addEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp)
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
document.addEventListener("pointerup", this.onLinkButtonUp);
-
}
onLinkButtonUp = (e: PointerEvent): void => {
- console.log("up");
- document.removeEventListener("pointermove", this.onLinkButtonMoved)
- document.removeEventListener("pointerup", this.onLinkButtonUp)
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
e.stopPropagation();
}
+ onLinkButtonMoved = async (e: PointerEvent) => {
+ if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) {
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
- onLinkButtonMoved = (e: PointerEvent): void => {
- console.log("moved");
- let dragData: { [id: string]: any } = {};
- dragData["linkSourceDoc"] = SelectionManager.SelectedDocuments()[0];
- if (this._linkButton.current != null) {
- DragManager.StartDrag(this._linkButton.current, dragData, {
- handlers: {
- dragComplete: action(() => { }),
- },
- hideSource: false
- })
+ DragLinksAsDocuments(this._linkButton.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document);
}
- document.removeEventListener("pointermove", this.onLinkButtonMoved)
- document.removeEventListener("pointerup", this.onLinkButtonUp)
e.stopPropagation();
}
-
onPointerMove = (e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
@@ -109,75 +388,84 @@ export class DocumentDecorations extends React.Component {
let dX = 0, dY = 0, dW = 0, dH = 0;
- switch (this._resizer) {
+ switch (this._resizing) {
case "":
break;
case "documentDecorations-topLeftResizer":
- dX = -1
- dY = -1
- dW = -(e.movementX)
- dH = -(e.movementY)
+ dX = -1;
+ dY = -1;
+ dW = -(e.movementX);
+ dH = -(e.movementY);
break;
case "documentDecorations-topRightResizer":
- dW = e.movementX
- dY = -1
- dH = -(e.movementY)
+ dW = e.movementX;
+ dY = -1;
+ dH = -(e.movementY);
break;
case "documentDecorations-topResizer":
- dY = -1
- dH = -(e.movementY)
+ dY = -1;
+ dH = -(e.movementY);
break;
case "documentDecorations-bottomLeftResizer":
- dX = -1
- dW = -(e.movementX)
- dH = e.movementY
+ dX = -1;
+ dW = -(e.movementX);
+ dH = e.movementY;
break;
case "documentDecorations-bottomRightResizer":
- dW = e.movementX
- dH = e.movementY
+ dW = e.movementX;
+ dH = e.movementY;
break;
case "documentDecorations-bottomResizer":
- dH = e.movementY
+ dH = e.movementY;
break;
case "documentDecorations-leftResizer":
- dX = -1
- dW = -(e.movementX)
+ dX = -1;
+ dW = -(e.movementX);
break;
case "documentDecorations-rightResizer":
- dW = e.movementX
+ dW = e.movementX;
break;
}
+ runInAction(() => FormattedTextBox.InputBoxOverlay = undefined);
SelectionManager.SelectedDocuments().forEach(element => {
- const rect = element.screenRect();
+ const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
+
if (rect.width !== 0) {
- let doc = element.props.Document;
- let width = doc.GetNumber(KeyStore.Width, 0);
- let nwidth = doc.GetNumber(KeyStore.NativeWidth, 0);
- let nheight = doc.GetNumber(KeyStore.NativeHeight, 0);
- let height = doc.GetNumber(KeyStore.Height, nwidth ? nheight / nwidth * width : 0);
- let x = doc.GetOrCreate(KeyStore.X, NumberField);
- let y = doc.GetOrCreate(KeyStore.Y, NumberField);
+ let doc = PositionDocument(element.props.Document);
+ let width = FieldValue(doc.width, 0);
+ let nwidth = FieldValue(doc.nativeWidth, 0);
+ let nheight = FieldValue(doc.nativeHeight, 0);
+ let height = FieldValue(doc.height, nwidth ? nheight / nwidth * width : 0);
+ let x = FieldValue(doc.x, 0);
+ let y = FieldValue(doc.y, 0);
let scale = width / rect.width;
let actualdW = Math.max(width + (dW * scale), 20);
let actualdH = Math.max(height + (dH * scale), 20);
- x.Data += dX * (actualdW - width);
- y.Data += dY * (actualdH - height);
- var nativeWidth = doc.GetNumber(KeyStore.NativeWidth, 0);
- var nativeHeight = doc.GetNumber(KeyStore.NativeHeight, 0);
+ x += dX * (actualdW - width);
+ y += dY * (actualdH - height);
+ doc.x = x;
+ doc.y = y;
+ var nativeWidth = FieldValue(doc.nativeWidth, 0);
+ var nativeHeight = FieldValue(doc.nativeHeight, 0);
if (nativeWidth > 0 && nativeHeight > 0) {
- if (Math.abs(dW) > Math.abs(dH))
+ if (Math.abs(dW) > Math.abs(dH)) {
actualdH = nativeHeight / nativeWidth * actualdW;
+ }
else actualdW = nativeWidth / nativeHeight * actualdH;
}
- doc.SetNumber(KeyStore.Width, actualdW);
- doc.SetNumber(KeyStore.Height, actualdH);
+ doc.width = actualdW;
+ doc.height = actualdH;
}
- })
+ });
}
+ @action
onPointerUp = (e: PointerEvent): void => {
e.stopPropagation();
+ this._resizing = "";
+ this.Interacting = false;
+ SelectionManager.ReselectAll();
if (e.button === 0) {
e.preventDefault();
this._isPointerDown = false;
@@ -186,6 +474,22 @@ export class DocumentDecorations extends React.Component {
}
}
+ @computed
+ get selectionTitle(): string {
+ if (SelectionManager.SelectedDocuments().length === 1) {
+ let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
+ if (typeof field === "string") {
+ return field;
+ }
+ else if (typeof field === "number") {
+ return field.toString();
+ }
+ } else if (SelectionManager.SelectedDocuments().length > 1) {
+ return "-multiple-";
+ }
+ return "-unset-";
+ }
+
changeFlyoutContent = (): void => {
}
@@ -195,33 +499,79 @@ export class DocumentDecorations extends React.Component {
render() {
var bounds = this.Bounds;
- if (this.Hidden) {
- return (null);
- }
- if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
- console.log("DocumentDecorations: Bounds Error")
+ let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
+ if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
return (null);
}
+ let minimizeIcon = (
+ <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
+ {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
+ </div>);
let linkButton = null;
if (SelectionManager.SelectedDocuments().length > 0) {
let selFirst = SelectionManager.SelectedDocuments()[0];
+ let linkToSize = Cast(selFirst.props.Document.linkedToDocs, listSpec(Doc), []).length;
+ let linkFromSize = Cast(selFirst.props.Document.linkedFromDocs, listSpec(Doc), []).length;
+ let linkCount = linkToSize + linkFromSize;
linkButton = (<Flyout
anchorPoint={anchorPoints.RIGHT_TOP}
- content={
- <LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent}>
- </LinkMenu>
- }>
- <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} ref={this._linkButton} />
- </Flyout>);
- }
- return (
- <div id="documentDecorations-container" style={{
- width: (bounds.r - bounds.x + 40) + "px",
- height: (bounds.b - bounds.y + 40) + "px",
- left: bounds.x - 20,
- top: bounds.y - 20,
+ content={<LinkMenu docView={selFirst}
+ changeFlyout={this.changeFlyoutContent} />}>
+ <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
+ </Flyout >);
+ }
+
+ let templates: Map<Template, boolean> = new Map();
+ Array.from(Object.values(Templates.TemplateList)).map(template => {
+ let sorted = SelectionManager.ViewsSortedVertically().slice().sort((doc1, doc2) => {
+ if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
+ if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
+ return 0;
+ });
+ let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => {
+ let temps = doc.props.Document.templates;
+ if (temps instanceof List) {
+ temps.map(temp => {
+ if (temp !== Templates.Bullet.Layout || i === 0) {
+ res.push(temp);
+ }
+ });
+ }
+ return res;
+ }, [] as string[]);
+ let checked = false;
+ docTemps.forEach(temp => {
+ if (template.Layout === temp) {
+ checked = true;
+ }
+ });
+ templates.set(template, checked);
+ });
+
+ return (<div className="documentDecorations">
+ <div className="documentDecorations-background" style={{
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2,
+ pointerEvents: this.Interacting ? "none" : "all",
+ zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
+ }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
+ </div>
+ <div className="documentDecorations-container" style={{
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
+ opacity: this._opacity
}}>
+ {minimizeIcon}
+
+ {this._edtingTitle ?
+ <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this._title} onBlur={this.titleBlur} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> :
+ <div className="title" onPointerDown={this.onTitleDown} ><span>{`${this.selectionTitle}`}</span></div>}
+ <div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-topRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
@@ -231,10 +581,19 @@ export class DocumentDecorations extends React.Component {
<div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
-
- {linkButton}
-
+ <div className="link-button-container">
+ <div className="linkButtonWrapper">
+ <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div>
+ </div>
+ <div className="linkButtonWrapper">
+ <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>
+ <FontAwesomeIcon className="fa-icon-link" icon="link" size="sm" />
+ </div>
+ </div>
+ <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
+ </div>
</div >
- )
+ </div>
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
new file mode 100644
index 000000000..dfa110f8d
--- /dev/null
+++ b/src/client/views/EditableView.scss
@@ -0,0 +1,20 @@
+.editableView-container-editing, .editableView-container-editing-oneLine {
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ hyphens: auto;
+ overflow: hidden;
+}
+.editableView-container-editing-oneLine {
+ span {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display:block;
+ }
+ input {
+ display:block;
+ }
+}
+.editableView-input {
+ width: 100%;
+} \ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 84b1b91c3..78143ccda 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,6 +1,7 @@
-import React = require('react')
+import React = require('react');
import { observer } from 'mobx-react';
-import { observable, action } from 'mobx';
+import { observable, action, trace } from 'mobx';
+import "./EditableView.scss";
export interface EditableProps {
/**
@@ -15,11 +16,15 @@ export interface EditableProps {
* */
SetValue(value: string): boolean;
+ OnFillDown?(value: string): void;
+
/**
* The contents to render when not editing
*/
contents: any;
- height: number
+ height: number;
+ display?: string;
+ oneLine?: boolean;
}
/**
@@ -34,26 +39,31 @@ export class EditableView extends React.Component<EditableProps> {
@action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key == "Enter" && !e.ctrlKey) {
- if (this.props.SetValue(e.currentTarget.value)) {
+ if (e.key === "Enter") {
+ if (!e.ctrlKey) {
+ if (this.props.SetValue(e.currentTarget.value)) {
+ this.editing = false;
+ }
+ } else if (this.props.OnFillDown) {
+ this.props.OnFillDown(e.currentTarget.value);
this.editing = false;
}
- } else if (e.key == "Escape") {
+ } else if (e.key === "Escape") {
this.editing = false;
}
}
render() {
if (this.editing) {
- return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
- style={{ display: "inline" }}></input>
+ return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
+ style={{ display: this.props.display }} />;
} else {
return (
- <div className="editableView-container-editing" style={{ display: "inline", height: "100%", maxHeight: `${this.props.height}` }}
- onClick={action(() => this.editing = true)}>
- {this.props.contents}
+ <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
+ onClick={action(() => this.editing = true)} >
+ <span>{this.props.contents}</span>
</div>
- )
+ );
}
}
} \ No newline at end of file
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index f654b194b..2c550051c 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,32 +1,35 @@
-.inking-canvas {
- position: fixed;
- top: -50000px;
- left: -50000px; // z-index: 99; //overlays ink on top of everything
- svg {
- width: 100000px;
- height: 100000px;
- .highlight {
- mix-blend-mode: multiply;
- }
- }
-}
+@import "globalCssVariables";
-.inking-control {
+.inkingCanvas {
+ opacity:0.99;
+}
+.inkingCanvas-paths-ink, .inkingCanvas-paths-markers, .inkingCanvas-noSelect, .inkingCanvas-canSelect {
position: absolute;
- right: 0;
- bottom: 75px;
- text-align: right;
- .ink-panel {
- margin-top: 12px;
- &:first {
- margin-top: 0;
- }
- }
- .ink-size {
- display: flex;
- justify-content: space-between;
- input {
- width: 85%;
- }
- }
-} \ No newline at end of file
+ top: 0;
+ left:0;
+ width: 8192px;
+ height: 8192px;
+ cursor:"crosshair";
+ pointer-events: auto;
+
+}
+.inkingCanvas-canSelect,
+.inkingCanvas-noSelect {
+ top:-50000px;
+ left:-50000px;
+ width: 100000px;
+ height: 100000px;
+}
+.inkingCanvas-noSelect {
+ pointer-events: none;
+ cursor: "arrow";
+}
+.inkingCanvas-paths-ink, .inkingCanvas-paths-markers {
+ pointer-events: none;
+ z-index: 10000; // overlays ink on top of everything
+ cursor: "arrow";
+}
+.inkingCanvas-paths-markers {
+ mix-blend-mode: multiply;
+}
+
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 0d87c1239..afe3e3ecb 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -1,120 +1,140 @@
+import { action, computed, trace, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { action, computed } from "mobx";
-import { InkingControl } from "./InkingControl";
-import React = require("react");
+import { Utils } from "../../Utils";
import { Transform } from "../util/Transform";
-import { Document } from "../../fields/Document";
-import { KeyStore } from "../../fields/KeyStore";
-import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField";
-import { JsxArgs } from "./nodes/DocumentView";
+import "./InkingCanvas.scss";
+import { InkingControl } from "./InkingControl";
import { InkingStroke } from "./InkingStroke";
-import "./InkingCanvas.scss"
-import { CollectionDockingView } from "./collections/CollectionDockingView";
-import { Utils } from "../../Utils";
-import { FieldWaiting } from "../../fields/Field";
-import { getMapLikeKeys } from "mobx/lib/internal";
-
+import React = require("react");
+import { undoBatch, UndoManager } from "../util/UndoManager";
+import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
interface InkCanvasProps {
getScreenTransform: () => Transform;
- Document: Document;
+ Document: Doc;
+ children: () => JSX.Element[];
}
@observer
export class InkingCanvas extends React.Component<InkCanvasProps> {
-
- private _isDrawing: boolean = false;
- private _idGenerator: string = "";
-
- constructor(props: Readonly<InkCanvasProps>) {
- super(props);
+ maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
+ @observable inkMidX: number = 0;
+ @observable inkMidY: number = 0;
+ private previousState?: Map<string, StrokeData>;
+ private _currentStrokeId: string = "";
+ public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
+ return stroke.pathData.reduce((inside: boolean, val) => inside ||
+ (selRect.left < val.x && selRect.left + selRect.width > val.x &&
+ selRect.top < val.y && selRect.top + selRect.height > val.y)
+ , false);
}
-
- @computed
- get inkData(): StrokeMap {
- let map = this.props.Document.GetT(KeyStore.Ink, InkField);
- if (!map || map === FieldWaiting) {
- return new Map;
- }
- return new Map(map.Data);
- }
-
- set inkData(value: StrokeMap) {
- this.props.Document.SetData(KeyStore.Ink, value, InkField);
+ public static StrokeRect(stroke: StrokeData): { left: number, top: number, right: number, bottom: number } {
+ return stroke.pathData.reduce((bounds: { left: number, top: number, right: number, bottom: number }, val) =>
+ ({
+ left: Math.min(bounds.left, val.x), top: Math.min(bounds.top, val.y),
+ right: Math.max(bounds.right, val.x), bottom: Math.max(bounds.bottom, val.y)
+ })
+ , { left: Number.MAX_VALUE, top: Number.MAX_VALUE, right: -Number.MAX_VALUE, bottom: -Number.MAX_VALUE });
}
componentDidMount() {
- document.addEventListener("mouseup", this.handleMouseUp);
+ PromiseValue(Cast(this.props.Document.ink, InkField)).then(ink => runInAction(() => {
+ if (ink) {
+ let bounds = Array.from(ink.inkData).reduce(([mix, max, miy, may], [id, strokeData]) =>
+ strokeData.pathData.reduce(([mix, max, miy, may], p) =>
+ [Math.min(mix, p.x), Math.max(max, p.x), Math.min(miy, p.y), Math.max(may, p.y)],
+ [mix, max, miy, may]),
+ [Number.MAX_VALUE, Number.MIN_VALUE, Number.MAX_VALUE, Number.MIN_VALUE]);
+ this.inkMidX = (bounds[0] + bounds[1]) / 2;
+ this.inkMidY = (bounds[2] + bounds[3]) / 2;
+ }
+ }));
}
- componentWillUnmount() {
- document.removeEventListener("mouseup", this.handleMouseUp);
+ @computed
+ get inkData(): Map<string, StrokeData> {
+ let map = Cast(this.props.Document.ink, InkField);
+ return !map ? new Map : new Map(map.inkData);
}
+ set inkData(value: Map<string, StrokeData>) {
+ Doc.SetOnPrototype(this.props.Document, "ink", new InkField(value));
+ }
@action
- handleMouseDown = (e: React.PointerEvent): void => {
- if (e.button != 0 ||
- InkingControl.Instance.selectedTool === InkTool.None) {
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.button !== 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) {
return;
}
- e.stopPropagation()
- if (InkingControl.Instance.selectedTool === InkTool.Eraser) {
- return
- }
- e.stopPropagation()
- const point = this.relativeCoordinatesForEvent(e);
- // start the new line, saves a uuid to represent the field of the stroke
- this._idGenerator = Utils.GenerateGuid();
- let data = this.inkData;
- data.set(this._idGenerator,
- {
- pathData: [point],
+ document.addEventListener("pointermove", this.onPointerMove, true);
+ document.addEventListener("pointerup", this.onPointerUp, true);
+ e.stopPropagation();
+ e.preventDefault();
+
+ this.previousState = this.inkData;
+
+ if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
+ // start the new line, saves a uuid to represent the field of the stroke
+ this._currentStrokeId = Utils.GenerateGuid();
+ const data = this.inkData;
+ data.set(this._currentStrokeId, {
+ pathData: [this.relativeCoordinatesForEvent(e.clientX, e.clientY)],
color: InkingControl.Instance.selectedColor,
width: InkingControl.Instance.selectedWidth,
tool: InkingControl.Instance.selectedTool,
- page: this.props.Document.GetNumber(KeyStore.CurPage, 0)
+ page: NumCast(this.props.Document.curPage, -1)
});
- this.inkData = data;
- this._isDrawing = true;
+ this.inkData = data;
+ }
}
@action
- handleMouseMove = (e: React.PointerEvent): void => {
- if (!this._isDrawing ||
- InkingControl.Instance.selectedTool === InkTool.None) {
- return;
- }
- e.stopPropagation()
- if (InkingControl.Instance.selectedTool === InkTool.Eraser) {
- return
+ onPointerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onPointerMove, true);
+ document.removeEventListener("pointerup", this.onPointerUp, true);
+ let coord = this.relativeCoordinatesForEvent(e.clientX, e.clientY);
+ if (Math.abs(coord.x - this.inkMidX) > 500 || Math.abs(coord.y - this.inkMidY) > 500) {
+ this.inkMidX = coord.x;
+ this.inkMidY = coord.y;
}
- const point = this.relativeCoordinatesForEvent(e);
-
- // add points to new line as it is being drawn
- let data = this.inkData;
- let strokeData = data.get(this._idGenerator);
- if (strokeData) {
- strokeData.pathData.push(point);
- data.set(this._idGenerator, strokeData);
- }
-
- this.inkData = data;
+ e.stopPropagation();
+ e.preventDefault();
+
+ const batch = UndoManager.StartBatch("One ink stroke");
+ const oldState = this.previousState || new Map;
+ this.previousState = undefined;
+ const newState = this.inkData;
+ UndoManager.AddEvent({
+ undo: () => this.inkData = oldState,
+ redo: () => this.inkData = newState,
+ });
+ batch.end();
}
@action
- handleMouseUp = (e: MouseEvent): void => {
- this._isDrawing = false;
+ onPointerMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ if (InkingControl.Instance.selectedTool !== InkTool.Eraser) {
+ let data = this.inkData; // add points to new line as it is being drawn
+ let strokeData = data.get(this._currentStrokeId);
+ if (strokeData) {
+ strokeData.pathData.push(this.relativeCoordinatesForEvent(e.clientX, e.clientY));
+ data.set(this._currentStrokeId, strokeData);
+ }
+ this.inkData = data;
+ }
}
- relativeCoordinatesForEvent = (e: React.MouseEvent): { x: number, y: number } => {
- let [x, y] = this.props.getScreenTransform().transformPoint(e.clientX, e.clientY);
- x += 50000
- y += 50000
+ relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
+ let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
return { x, y };
}
+ @undoBatch
@action
removeLine = (id: string): void => {
let data = this.inkData;
@@ -122,50 +142,41 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
this.inkData = data;
}
- render() {
- // styling for cursor
- let canvasStyle = {};
- if (InkingControl.Instance.selectedTool === InkTool.None) {
- canvasStyle = { pointerEvents: "none" };
- } else {
- canvasStyle = { pointerEvents: "auto", cursor: "crosshair" };
- }
-
- // get data from server
- // let inkField = this.props.Document.GetT(KeyStore.Ink, InkField);
- // if (!inkField || inkField == "<Waiting>") {
- // return (<div className="inking-canvas" style={canvasStyle}
- // onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} >
- // <svg>
- // </svg>
- // </div >)
- // }
-
- let lines = this.inkData;
-
- // parse data from server
- let paths: Array<JSX.Element> = []
- let curPage = this.props.Document.GetNumber(KeyStore.CurPage, 0)
- Array.from(lines).map(item => {
- let id = item[0];
- let strokeData = item[1];
- if (strokeData.page == 0 || strokeData.page == curPage)
+ @computed
+ get drawnPaths() {
+ let curPage = NumCast(this.props.Document.curPage, -1);
+ let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
+ if (strokeData.page === -1 || strokeData.page === curPage) {
paths.push(<InkingStroke key={id} id={id}
line={strokeData.pathData}
+ count={strokeData.pathData.length}
+ offsetX={this.maxCanvasDim - this.inkMidX}
+ offsetY={this.maxCanvasDim - this.inkMidY}
color={strokeData.color}
width={strokeData.width}
tool={strokeData.tool}
- deleteCallback={this.removeLine} />)
- })
+ deleteCallback={this.removeLine} />);
+ }
+ return paths;
+ }, [] as JSX.Element[]);
+ return [<svg className={`inkingCanvas-paths-ink`} key="Pens"
+ style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
+ {paths.filter(path => path.props.tool !== InkTool.Highlighter)}
+ </svg>,
+ <svg className={`inkingCanvas-paths-markers`} key="Markers"
+ style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
+ {paths.filter(path => path.props.tool === InkTool.Highlighter)}
+ </svg>];
+ }
+ render() {
+ let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect";
return (
-
- <div className="inking-canvas" style={canvasStyle}
- onPointerDown={this.handleMouseDown} onPointerMove={this.handleMouseMove} >
- <svg>
- {paths}
- </svg>
+ <div className="inkingCanvas" >
+ <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
+ {this.props.children()}
+ {this.drawnPaths}
</div >
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss
new file mode 100644
index 000000000..ba4ec41af
--- /dev/null
+++ b/src/client/views/InkingControl.scss
@@ -0,0 +1,135 @@
+@import "globalCssVariables";
+.inking-control {
+ position: absolute;
+ left: 70px;
+ bottom: 70px;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ label,
+ input,
+ option {
+ font-size: 12px;
+ }
+ input[type="range"] {
+ -webkit-appearance: none;
+ background-color: transparent;
+ vertical-align: middle;
+ margin-top: 8px;
+ &:focus {
+ outline: none;
+ }
+ &::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 3px;
+ border-radius: 1.5px;
+ cursor: pointer;
+ background: $intermediate-color;
+ }
+ &::-webkit-slider-thumb {
+ height: 12px;
+ width: 12px;
+ border: 1px solid $intermediate-color;
+ border-radius: 6px;
+ background: $light-color;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -4px;
+ }
+ &::-moz-range-track {
+ width: 100%;
+ height: 3px;
+ border-radius: 1.5px;
+ cursor: pointer;
+ background: $light-color;
+ }
+ &::-moz-range-thumb {
+ height: 12px;
+ width: 12px;
+ border: 1px solid $intermediate-color;
+ border-radius: 6px;
+ background: $light-color;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -4px;
+ }
+ }
+ input[type="text"] {
+ border: none;
+ padding: 0 0px;
+ background: transparent;
+ color: $dark-color;
+ font-size: 12px;
+ margin-top: 4px;
+ }
+ .ink-panel {
+ margin: 6px 12px 6px 0;
+ height: 30px;
+ vertical-align: middle;
+ line-height: 36px;
+ padding: 0 10px;
+ color: $intermediate-color;
+ &:first {
+ margin-top: 0;
+ }
+ }
+ .ink-tools {
+ display: flex;
+ background-color: transparent;
+ border-radius: 0;
+ padding: 0;
+ button {
+ height: 36px;
+ padding: 0px;
+ padding-bottom: 3px;
+ margin-left: 10px;
+ background-color: transparent;
+ color: $intermediate-color;
+ }
+ button:hover {
+ transform: scale(1.15);
+ }
+ }
+ .ink-size {
+ display: flex;
+ justify-content: space-between;
+ input[type="text"] {
+ width: 42px;
+ }
+ >* {
+ margin-right: 6px;
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+ .ink-color {
+ display: flex;
+ position: relative;
+ padding-right: 0;
+ label {
+ margin-right: 6px;
+ }
+ .ink-color-display {
+ border-radius: 11px;
+ width: 22px;
+ height: 22px;
+ margin-top: 6px;
+ cursor: pointer;
+ text-align: center; // span {
+ // color: $light-color;
+ // font-size: 8px;
+ // user-select: none;
+ // }
+ }
+ .ink-color-picker {
+ background-color: $light-color;
+ border-radius: 5px;
+ padding: 12px;
+ position: absolute;
+ bottom: 36px;
+ left: -3px;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 955831fb6..17d4a1e49 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,20 +1,30 @@
import { observable, action, computed } from "mobx";
+
import { CirclePicker, ColorResult } from 'react-color';
import React = require("react");
-import "./InkingCanvas.scss"
-import { InkTool } from "../../fields/InkField";
import { observer } from "mobx-react";
+import "./InkingControl.scss";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
+import { SelectionManager } from "../util/SelectionManager";
+import { InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+
+library.add(faPen, faHighlighter, faEraser, faBan);
@observer
export class InkingControl extends React.Component {
static Instance: InkingControl = new InkingControl({});
@observable private _selectedTool: InkTool = InkTool.None;
- @observable private _selectedColor: string = "#f44336";
+ @observable private _selectedColor: string = "rgb(244, 67, 54)";
@observable private _selectedWidth: string = "25";
+ @observable private _open: boolean = false;
+ @observable private _colorPickerDisplay = false;
constructor(props: Readonly<{}>) {
super(props);
- InkingControl.Instance = this
+ InkingControl.Instance = this;
}
@action
@@ -25,6 +35,9 @@ export class InkingControl extends React.Component {
@action
switchColor = (color: ColorResult): void => {
this._selectedColor = color.hex;
+ SelectionManager.SelectedDocuments().forEach(doc =>
+ doc.props.ContainingCollectionView && Doc.SetOnPrototype(doc.props.Document, "backgroundColor", color.hex)
+ );
}
@action
@@ -49,29 +62,51 @@ export class InkingControl extends React.Component {
selected = (tool: InkTool) => {
if (this._selectedTool === tool) {
- return { backgroundColor: "black", color: "white" }
+ return { color: "#61aaa3" };
}
- return {}
+ return {};
+ }
+
+ @action
+ toggleDisplay = () => {
+ this._open = !this._open;
+ }
+
+
+ @action
+ toggleColorPicker = () => {
+ this._colorPickerDisplay = !this._colorPickerDisplay;
}
render() {
return (
- <div className="inking-control">
- <div className="ink-tools ink-panel">
- <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}>Pen</button>
- <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}>Highlighter</button>
- <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}>Eraser</button>
- <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}> None</button>
- </div>
- <div className="ink-size ink-panel">
- <label htmlFor="stroke-width">Size</label>
- <input type="range" min="1" max="100" defaultValue="25" name="stroke-width"
+ <ul className="inking-control" style={this._open ? { display: "flex" } : { display: "none" }}>
+ <li className="ink-tools ink-panel">
+ <div className="ink-tool-buttons">
+ <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button>
+ <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Highlighter" /></button>
+ <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Eraser" /></button>
+ <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="lg" title="Pointer" /></button>
+ </div>
+ </li>
+ <li className="ink-color ink-panel">
+ <label>COLOR: </label>
+ <div className="ink-color-display" style={{ backgroundColor: this._selectedColor }}
+ onClick={() => this.toggleColorPicker()}>
+ {/* {this._colorPickerDisplay ? <span>&#9660;</span> : <span>&#9650;</span>} */}
+ </div>
+ <div className="ink-color-picker" style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}>
+ <CirclePicker onChange={this.switchColor} circleSize={22} width={"220"} />
+ </div>
+ </li>
+ <li className="ink-size ink-panel">
+ <label htmlFor="stroke-width">SIZE: </label>
+ <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width"
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
+ <input type="range" min="1" max="100" value={this._selectedWidth} name="stroke-width"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
- </div>
- <div className="ink-color ink-panel">
- <CirclePicker onChange={this.switchColor} />
- </div>
- </div>
- )
+ </li>
+ </ul >
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/InkingStroke.scss b/src/client/views/InkingStroke.scss
new file mode 100644
index 000000000..cdbfdcff3
--- /dev/null
+++ b/src/client/views/InkingStroke.scss
@@ -0,0 +1,3 @@
+.inkingStroke-marker {
+ mix-blend-mode: multiply
+} \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d724421d3..37b1f5899 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,12 +1,16 @@
import { observer } from "mobx-react";
-import { observable } from "mobx";
+import { observable, trace } from "mobx";
import { InkingControl } from "./InkingControl";
-import { InkTool } from "../../fields/InkField";
import React = require("react");
+import { InkTool } from "../../new_fields/InkField";
+import "./InkingStroke.scss";
interface StrokeProps {
+ offsetX: number;
+ offsetY: number;
id: string;
+ count: number;
line: Array<{ x: number, y: number }>;
color: string;
width: string;
@@ -21,23 +25,14 @@ export class InkingStroke extends React.Component<StrokeProps> {
@observable private _strokeColor: string = this.props.color;
@observable private _strokeWidth: string = this.props.width;
- private _canvasColor: string = "#cdcdcd";
-
- deleteStroke = (e: React.MouseEvent): void => {
+ deleteStroke = (e: React.PointerEvent): void => {
if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) {
this.props.deleteCallback(this.props.id);
}
}
parseData = (line: Array<{ x: number, y: number }>): string => {
- if (line.length === 0) {
- return "";
- }
- const pathData = "M " +
- line.map(p => {
- return p.x + " " + p.y;
- }).join(" L ");
- return pathData;
+ return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ");
}
createStyle() {
@@ -48,19 +43,20 @@ export class InkingStroke extends React.Component<StrokeProps> {
fill: "none",
stroke: this._strokeColor,
strokeWidth: this._strokeWidth + "px",
- }
+ };
}
}
-
render() {
let pathStyle = this.createStyle();
let pathData = this.parseData(this.props.line);
+ let pathlength = this.props.count; // bcz: this is needed to force reactions to the line data changes
+ let marker = this.props.tool === InkTool.Highlighter ? "-marker" : "";
+ let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none";
return (
- <path className={(this._strokeTool === InkTool.Highlighter) ? "highlight" : ""}
- d={pathData} style={pathStyle} strokeLinejoin="round" strokeLinecap="round"
- onMouseOver={this.deleteStroke} onMouseDown={this.deleteStroke} />
- )
+ <path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
+ onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 4334ed299..d63b0147b 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,51 +1,196 @@
+@import "globalCssVariables";
+@import "nodeModuleOverrides";
+
html,
body {
width: 100%;
height: 100%;
- overflow: hidden;
- font-family: 'Hind Siliguri', sans-serif;
+ overflow: auto;
+ font-family: $sans-serif;
margin: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
}
-h1 {
- font-size: 50px;
- position: fixed;
- top: 30px;
- left: 50%;
- transform: translateX(-50%);
- color: black;
- text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
- z-index: 9999;
- font-family: 'Fjalla One', sans-serif;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
+div {
user-select: none;
}
+#dash-title {
+ position: absolute;
+ right: 46.5%;
+ letter-spacing: 3px;
+ top: 9px;
+ font-size: 12px;
+ color: $alt-accent;
+ z-index: 9999;
+}
+
+.jsx-parser {
+ width: 100%;
+ pointer-events: none;
+ border-radius: inherit;
+}
+
p {
margin: 0px;
padding: 0px;
}
+
::-webkit-scrollbar {
-webkit-appearance: none;
- height:5px;
- width:5px;
+ height: 5px;
+ width: 10px;
}
+
::-webkit-scrollbar-thumb {
border-radius: 2px;
- background-color: rgba(0,0,0,.5);
+ background-color: rgba(0, 0, 0, 0.5);
+}
+
+// button stuff
+button {
+ background: $dark-color;
+ outline: none;
+ border: 0px;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ padding: 10px;
+ transition: transform 0.2s;
}
-.main-buttonDiv {
+button:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
+.clear-db-button {
position: absolute;
- width: 150px;
- left: 0px;
+ right: 45%;
+ bottom: 3%;
+ font-size: 50%;
+}
+
+.round-button {
+ width: 36px;
+ height: 36px;
+ border-radius: 18px;
+ font-size: 15px;
+}
+
+.round-button:hover {
+ transform: scale(1.15);
}
+
+.add-button {
+ position: relative;
+ margin-right: 10px;
+}
+
.main-undoButtons {
position: absolute;
width: 150px;
right: 0px;
}
+
+//toolbar stuff
+#toolbar {
+ position: absolute;
+ bottom: 62px;
+ left: 24px;
+
+ .toolbar-button {
+ display: block;
+ margin-bottom: 10px;
+ }
+}
+
+// add nodes menu. Note that the + button is actually an input label, not an actual button.
+#add-nodes-menu {
+ position: absolute;
+ bottom: 22px;
+ left: 24px;
+
+ label {
+ background: $dark-color;
+ color: $light-color;
+ display: inline-block;
+ border-radius: 18px;
+ font-size: 25px;
+ width: 36px;
+ height: 36px;
+ margin-right: 10px;
+ cursor: pointer;
+ transition: transform 0.2s;
+ }
+
+ label p {
+ padding-left: 10.5px;
+ }
+
+ label:hover {
+ background: $main-accent;
+ transform: scale(1.15);
+ }
+
+ input {
+ display: none;
+ }
+
+ input:not(:checked)~#add-options-content {
+ display: none;
+ }
+
+ input:checked~label {
+ transform: rotate(45deg);
+ transition: transform 0.5s;
+ cursor: pointer;
+ }
+}
+
+#root {
+ overflow: visible;
+}
+
+#main-div {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: scroll;
+ z-index: 1;
+}
+
+#mainContent-div {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+#add-options-content {
+ display: table;
+ opacity: 1;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ float: right;
+ bottom: 0.3em;
+ margin-bottom: -1.68em;
+}
+
+ul#add-options-list {
+ list-style: none;
+ padding: 5 0 0 0;
+
+ li {
+ display: inline-block;
+ padding: 0;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index b78f59681..177047a9a 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,117 +1,281 @@
-import { action, configure } from 'mobx';
+import { IconName, library } from '@fortawesome/fontawesome-svg-core';
+import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, configure, observable, runInAction, trace } from 'mobx';
+import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import { Document } from '../../fields/Document';
-import { KeyStore } from '../../fields/KeyStore';
-import "./Main.scss";
-import { MessageStore } from '../../server/Message';
-import { Utils } from '../../Utils';
-import { Documents } from '../documents/Documents';
-import { Server } from '../Server';
-import { setupDrag } from '../util/DragManager';
+import Measure from 'react-measure';
+import * as request from 'request';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { RouteStore } from '../../server/RouteStore';
+import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
+import { Docs } from '../documents/Documents';
+import { SetupDrag, DragManager } from '../util/DragManager';
import { Transform } from '../util/Transform';
import { UndoManager } from '../util/UndoManager';
+import { PresentationView } from './PresentationView';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { ContextMenu } from './ContextMenu';
import { DocumentDecorations } from './DocumentDecorations';
-import { DocumentView } from './nodes/DocumentView';
-import "./Main.scss";
import { InkingControl } from './InkingControl';
+import "./Main.scss";
+import { MainOverlayTextBox } from './MainOverlayTextBox';
+import { DocumentView } from './nodes/DocumentView';
+import { PreviewCursor } from './PreviewCursor';
+import { SearchBox } from './SearchBox';
+import { SelectionManager } from '../util/SelectionManager';
+import { FieldResult, Field, Doc, Opt } from '../../new_fields/Doc';
+import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
+import { DocServer } from '../DocServer';
+import { listSpec } from '../../new_fields/Schema';
+import { Id } from '../../new_fields/RefField';
+import { HistoryUtil } from '../util/History';
+
+@observer
+export class Main extends React.Component {
+ public static Instance: Main;
+ @observable private _workspacesShown: boolean = false;
+ @observable public pwidth: number = 0;
+ @observable public pheight: number = 0;
-configure({ enforceActions: "observed" }); // causes errors to be generated when modifying an observable outside of an action
-window.addEventListener("drop", (e) => e.preventDefault(), false)
-window.addEventListener("dragover", (e) => e.preventDefault(), false)
-document.addEventListener("pointerdown", action(function (e: PointerEvent) {
- if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
- ContextMenu.Instance.clearItems()
+ @computed private get mainContainer(): Opt<Doc> {
+ return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
+ }
+ private set mainContainer(doc: Opt<Doc>) {
+ if (doc) {
+ if (!("presentationView" in doc)) {
+ doc.presentationView = new Doc();
+ }
+ CurrentUserUtils.UserDocument.activeWorkspace = doc;
+ }
}
-}), true)
+ constructor(props: Readonly<{}>) {
+ super(props);
+ Main.Instance = this;
+ // causes errors to be generated when modifying an observable outside of an action
+ configure({ enforceActions: "observed" });
+ if (window.location.pathname !== RouteStore.home) {
+ let pathname = window.location.pathname.split("/");
+ if (pathname.length > 1 && pathname[pathname.length - 2] === 'doc') {
+ CurrentUserUtils.MainDocId = pathname[pathname.length - 1];
+ }
+ }
-const mainDocId = "mainDoc";
-let mainContainer: Document;
-let mainfreeform: Document;
-Documents.initProtos(mainDocId, (res?: Document) => {
- if (res instanceof Document) {
- mainContainer = res;
- mainContainer.GetAsync(KeyStore.ActiveFrame, field => mainfreeform = field as Document);
+ library.add(faFont);
+ library.add(faImage);
+ library.add(faFilePdf);
+ library.add(faObjectGroup);
+ library.add(faTable);
+ library.add(faGlobeAsia);
+ library.add(faUndoAlt);
+ library.add(faRedoAlt);
+ library.add(faPenNib);
+ library.add(faFilm);
+ library.add(faMusic);
+ library.add(faTree);
+ this.initEventListeners();
+ this.initAuthenticationRouters();
}
- else {
- mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, mainDocId);
- // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
- setTimeout(() => {
- mainfreeform = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
+ initEventListeners = () => {
+ // window.addEventListener("pointermove", (e) => this.reportLocation(e))
+ window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
+ window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
+ window.addEventListener("keydown", (e) => {
+ if (e.key === "Escape") {
+ DragManager.AbortDrag();
+ SelectionManager.DeselectAll();
+ }
+ }, false); // drag event handler
+ // click interactions for the context menu
+ document.addEventListener("pointerdown", action(function (e: PointerEvent) {
+ if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
+ ContextMenu.Instance.clearItems();
+ }
+ }), true);
+ }
- var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(mainfreeform)] }] };
- mainContainer.SetText(KeyStore.Data, JSON.stringify(dockingLayout));
- mainContainer.Set(KeyStore.ActiveFrame, mainfreeform);
- }, 0);
+ initAuthenticationRouters = async () => {
+ // Load the user's active workspace, or create a new one if initial session after signup
+ if (!CurrentUserUtils.MainDocId) {
+ const doc = await Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc);
+ if (doc) {
+ this.openWorkspace(doc);
+ } else {
+ this.createNewWorkspace();
+ }
+ } else {
+ DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field =>
+ field instanceof Doc ? this.openWorkspace(field) :
+ this.createNewWorkspace(CurrentUserUtils.MainDocId));
+ }
}
- let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
- let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf"
- let weburl = "https://cs.brown.edu/courses/cs166/";
- let audiourl = "http://techslides.com/demos/samples/sample.mp3";
- let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
- let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
- let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
- let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
- let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" }));
- let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
- let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
- let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
- let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
- let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
- let addClick = (creator: () => Document) => action(() =>
- mainfreeform.GetList<Document>(KeyStore.Data, []).push(creator())
- );
-
- let imgRef = React.createRef<HTMLDivElement>();
- let pdfRef = React.createRef<HTMLDivElement>();
- let webRef = React.createRef<HTMLDivElement>();
- let textRef = React.createRef<HTMLDivElement>();
- let schemaRef = React.createRef<HTMLDivElement>();
- let videoRef = React.createRef<HTMLDivElement>();
- let audioRef = React.createRef<HTMLDivElement>();
- let colRef = React.createRef<HTMLDivElement>();
-
- ReactDOM.render((
- <div style={{ position: "absolute", width: "100%", height: "100%" }}>
- <DocumentView Document={mainContainer}
- AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
- ContentScaling={() => 1}
- PanelWidth={() => 0}
- PanelHeight={() => 0}
+ @action
+ createNewWorkspace = async (id?: string) => {
+ const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
+ if (list) {
+ let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
+ var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
+ let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
+ list.push(mainDoc);
+ // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+ setTimeout(() => {
+ this.openWorkspace(mainDoc);
+ let pendingDocument = Docs.SchemaDocument(["title"], [], { title: "New Mobile Uploads" });
+ mainDoc.optionalRightCollection = pendingDocument;
+ }, 0);
+ }
+ }
+
+ @action
+ openWorkspace = async (doc: Doc, fromHistory = false) => {
+ CurrentUserUtils.MainDocId = doc[Id];
+ this.mainContainer = doc;
+ fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], initializers: {} });
+ const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc);
+ // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
+ setTimeout(async () => {
+ if (col) {
+ const l = Cast(col.data, listSpec(Doc));
+ if (l && l.length > 0) {
+ CollectionDockingView.Instance.AddRightSplit(col);
+ }
+ }
+ }, 100);
+ }
+ @action
+ onResize = (r: any) => {
+ this.pwidth = r.offset.width;
+ this.pheight = r.offset.height;
+ }
+ getPWidth = () => {
+ return this.pwidth;
+ }
+ getPHeight = () => {
+ return this.pheight;
+ }
+ @computed
+ get mainContent() {
+ let mainCont = this.mainContainer;
+ let content = !mainCont ? (null) :
+ <DocumentView Document={mainCont}
+ toggleMinimized={emptyFunction}
+ addDocument={undefined}
+ addDocTab={emptyFunction}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={this.getPWidth}
+ PanelHeight={this.getPHeight}
isTopMost={true}
- SelectOnLoad={false}
- focus={() => { }}
- ContainingCollectionView={undefined} />
- <DocumentDecorations />
- <ContextMenu />
- <div className="main-buttonDiv" style={{ bottom: '0px' }} ref={imgRef} >
- <button onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>Add Image</button></div>
- <div className="main-buttonDiv" style={{ bottom: '25px' }} ref={webRef} >
- <button onPointerDown={setupDrag(webRef, addWebNode)} onClick={addClick(addWebNode)}>Add Web</button></div>
- <div className="main-buttonDiv" style={{ bottom: '50px' }} ref={textRef}>
- <button onPointerDown={setupDrag(textRef, addTextNode)} onClick={addClick(addTextNode)}>Add Text</button></div>
- <div className="main-buttonDiv" style={{ bottom: '75px' }} ref={colRef}>
- <button onPointerDown={setupDrag(colRef, addColNode)} onClick={addClick(addColNode)}>Add Collection</button></div>
- <div className="main-buttonDiv" style={{ bottom: '100px' }} ref={schemaRef}>
- <button onPointerDown={setupDrag(schemaRef, addSchemaNode)} onClick={addClick(addSchemaNode)}>Add Schema</button></div>
- <div className="main-buttonDiv" style={{ bottom: '125px' }} >
- <button onClick={clearDatabase}>Clear Database</button></div>
- <div className="main-buttonDiv" style={{ bottom: '175px' }} ref={videoRef}>
- <button onPointerDown={setupDrag(videoRef, addVideoNode)} onClick={addClick(addVideoNode)}>Add Video</button></div>
- <div className="main-buttonDiv" style={{ bottom: '200px' }} ref={audioRef}>
- <button onPointerDown={setupDrag(audioRef, addAudioNode)} onClick={addClick(addAudioNode)}>Add Audio</button></div>
- <div className="main-buttonDiv" style={{ bottom: '150px' }} ref={pdfRef}>
- <button onPointerDown={setupDrag(pdfRef, addPDFNode)} onClick={addClick(addPDFNode)}>Add PDF</button></div>
- <button className="main-undoButtons" style={{ bottom: '25px' }} onClick={() => UndoManager.Undo()}>Undo</button>
- <button className="main-undoButtons" style={{ bottom: '0px' }} onClick={() => UndoManager.Redo()}>Redo</button>
- <InkingControl />
- </div>),
- document.getElementById('root'));
-})
+ selectOnLoad={false}
+ focus={emptyFunction}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined} />;
+ const pres = mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined;
+ return <Measure offset onResize={this.onResize}>
+ {({ measureRef }) =>
+ <div ref={measureRef} id="mainContent-div">
+ {content}
+ {pres ? <PresentationView Document={pres} key="presentation" /> : null}
+ </div>
+ }
+ </Measure>;
+ }
+
+ /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
+ nodesMenu() {
+
+ let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
+ let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c27211_sample_explain.pdf";
+ let weburl = "https://cs.brown.edu/courses/cs166/";
+ let audiourl = "http://techslides.com/demos/samples/sample.mp3";
+ let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
+
+ let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
+ let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
+ let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" }));
+ let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
+ // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" }));
+ let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" }));
+ let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
+ let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
+ let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
+ let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
+
+ let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
+ [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
+ [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
+ [React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode],
+ [React.createRef<HTMLDivElement>(), "film", "Add Video", addVideoNode],
+ [React.createRef<HTMLDivElement>(), "music", "Add Audio", addAudioNode],
+ [React.createRef<HTMLDivElement>(), "globe-asia", "Add Web Clipping", addWebNode],
+ [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
+ [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
+ [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
+ ];
+
+ return < div id="add-nodes-menu" >
+ <input type="checkbox" id="add-menu-toggle" />
+ <label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
+
+ <div id="add-options-content">
+ <ul id="add-options-list">
+ {btns.map(btn =>
+ <li key={btn[1]} ><div ref={btn[0]}>
+ <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
+ <FontAwesomeIcon icon={btn[1]} size="sm" />
+ </button>
+ </div></li>)}
+ </ul>
+ </div>
+ </div >;
+ }
+
+ /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
+ @computed
+ get miscButtons() {
+ let logoutRef = React.createRef<HTMLDivElement>();
+
+ return [
+ <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>,
+ <div id="toolbar" key="toolbar">
+ <button className="toolbar-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button>
+ <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
+ <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
+ </div >,
+ <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div>,
+ <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+ <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
+ ];
+
+ }
+
+ render() {
+ return (
+ <div id="main-div">
+ <DocumentDecorations />
+ {this.mainContent}
+ <PreviewCursor />
+ <ContextMenu />
+ {this.nodesMenu()}
+ {this.miscButtons}
+ <InkingControl />
+ <MainOverlayTextBox />
+ </div>
+ );
+ }
+}
+
+(async () => {
+ await Docs.initProtos();
+ await CurrentUserUtils.loadCurrentUser();
+ ReactDOM.render(<Main />, document.getElementById('root'));
+})();
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
new file mode 100644
index 000000000..f6a746e63
--- /dev/null
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -0,0 +1,20 @@
+@import "globalCssVariables";
+.mainOverlayTextBox-textInput {
+ background-color: rgba(248, 6, 6, 0.001);
+ width: 200px;
+ height: 200px;
+ position:absolute;
+ overflow: visible;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ z-index: $mainTextInput-zindex;
+ .formattedTextBox-cont {
+ background-color: rgba(248, 6, 6, 0.001);
+ width: 100%;
+ height: 100%;
+ position:absolute;
+ top: 0;
+ left: 0;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
new file mode 100644
index 000000000..91f626737
--- /dev/null
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -0,0 +1,99 @@
+import { action, observable, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { emptyFunction, returnTrue, returnZero } from '../../Utils';
+import { DragManager } from '../util/DragManager';
+import { Transform } from '../util/Transform';
+import "normalize.css";
+import "./MainOverlayTextBox.scss";
+import { FormattedTextBox } from './nodes/FormattedTextBox';
+
+interface MainOverlayTextBoxProps {
+}
+
+@observer
+export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> {
+ public static Instance: MainOverlayTextBox;
+ @observable _textXf: () => Transform = () => Transform.Identity();
+ private _textFieldKey: string = "data";
+ private _textColor: string | null = null;
+ private _textTargetDiv: HTMLDivElement | undefined;
+ private _textProxyDiv: React.RefObject<HTMLDivElement>;
+
+ constructor(props: MainOverlayTextBoxProps) {
+ super(props);
+ this._textProxyDiv = React.createRef();
+ MainOverlayTextBox.Instance = this;
+ reaction(() => FormattedTextBox.InputBoxOverlay,
+ (box?: FormattedTextBox) => {
+ if (box) this.setTextDoc(box.props.fieldKey, box.CurrentDiv, box.props.ScreenToLocalTransform);
+ else this.setTextDoc();
+ });
+ }
+
+ @action
+ private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform) {
+ if (this._textTargetDiv) {
+ this._textTargetDiv.style.color = this._textColor;
+ }
+ this._textFieldKey = textFieldKey!;
+ this._textXf = tx ? tx : () => Transform.Identity();
+ this._textTargetDiv = div;
+ if (div) {
+ if (div.parentElement && div.parentElement instanceof HTMLDivElement && div.parentElement.id === "screenSpace") this._textXf = () => Transform.Identity();
+ this._textColor = div.style.color;
+ div.style.color = "transparent";
+ }
+ }
+
+ @action
+ textScroll = (e: React.UIEvent) => {
+ if (this._textProxyDiv.current && this._textTargetDiv) {
+ this._textTargetDiv.scrollTop = (e as any)._targetInst.stateNode.scrollTop;
+ }
+ }
+
+ textBoxDown = (e: React.PointerEvent) => {
+ if (e.button !== 0 || e.metaKey || e.altKey) {
+ document.addEventListener("pointermove", this.textBoxMove);
+ document.addEventListener('pointerup', this.textBoxUp);
+ }
+ }
+ @action
+ textBoxMove = (e: PointerEvent) => {
+ if (e.movementX > 1 || e.movementY > 1) {
+ document.removeEventListener("pointermove", this.textBoxMove);
+ document.removeEventListener('pointerup', this.textBoxUp);
+ let dragData = new DragManager.DocumentDragData(FormattedTextBox.InputBoxOverlay ? [FormattedTextBox.InputBoxOverlay.props.Document] : []);
+ const [left, top] = this._textXf().inverse().transformPoint(0, 0);
+ dragData.xOffset = e.clientX - left;
+ dragData.yOffset = e.clientY - top;
+ DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
+ }
+ textBoxUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.textBoxMove);
+ document.removeEventListener('pointerup', this.textBoxUp);
+ }
+
+ render() {
+ if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
+ let textRect = this._textTargetDiv.getBoundingClientRect();
+ let s = this._textXf().Scale;
+ return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} >
+ <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
+ style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
+ <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
+ selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
+ ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} />
+ </div>
+ </ div>;
+ }
+ else return (null);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/PresentationView.scss b/src/client/views/PresentationView.scss
new file mode 100644
index 000000000..fb4a851c4
--- /dev/null
+++ b/src/client/views/PresentationView.scss
@@ -0,0 +1,79 @@
+.presentationView-cont {
+ position: absolute;
+ background: white;
+ z-index: 1;
+ box-shadow: #AAAAAA .2vw .2vw .4vw;
+ right: 0;
+ top: 0;
+ bottom: 0;
+}
+
+.presentationView-item {
+ padding: 10px;
+ display: inline-block;
+ width: 100%;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all .1s;
+}
+
+.presentationView-listCont {
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+.presentationView-item:hover {
+ transition: all .1s;
+ background: #AAAAAA
+}
+
+.presentationView-selected {
+ background: gray;
+}
+
+.presentationView-heading {
+ background: lightseagreen;
+ padding: 10px;
+ display: inline-block;
+ width: 100%;
+}
+
+.presentationView-title {
+ padding-top: 3px;
+ padding-bottom: 3px;
+ font-size: 25px;
+ display: inline-block;
+}
+
+.presentation-icon {
+ float: right;
+}
+
+.presentationView-name {
+ font-size: 15px;
+ display: inline-block;
+}
+
+.presentation-button {
+ margin-right: 12.5%;
+ margin-left: 12.5%;
+ width: 25%;
+}
+
+.presentation-buttons {
+ padding: 10px;
+}
+
+.presentation-next:hover {
+ transition: all .1s;
+ background: #AAAAAA
+}
+
+.presentation-back:hover {
+ transition: all .1s;
+ background: #AAAAAA
+} \ No newline at end of file
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
new file mode 100644
index 000000000..7d0dc2913
--- /dev/null
+++ b/src/client/views/PresentationView.tsx
@@ -0,0 +1,150 @@
+import { observer } from "mobx-react";
+import React = require("react");
+import { observable, action, runInAction, reaction } from "mobx";
+import "./PresentationView.scss";
+import "./Main.tsx";
+import { DocumentManager } from "../util/DocumentManager";
+import { Utils } from "../../Utils";
+import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc";
+import { listSpec } from "../../new_fields/Schema";
+import { Cast, NumCast, FieldValue, PromiseValue, StrCast } from "../../new_fields/Types";
+import { Id } from "../../new_fields/RefField";
+import { List } from "../../new_fields/List";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+
+export interface PresViewProps {
+ Document: Doc;
+}
+
+interface PresListProps extends PresViewProps {
+ deleteDocument(index: number): void;
+ gotoDocument(index: number): void;
+}
+
+
+@observer
+/**
+ * Component that takes in a document prop and a boolean whether it's collapsed or not.
+ */
+class PresentationViewList extends React.Component<PresListProps> {
+
+ /**
+ * Renders a single child document. It will just append a list element.
+ * @param document The document to render.
+ */
+ renderChild = (document: Doc, index: number) => {
+ let title = document.title;
+
+ //to get currently selected presentation doc
+ let selected = NumCast(this.props.Document.selectedDoc, 0);
+
+ let className = "presentationView-item";
+ if (selected === index) {
+ //this doc is selected
+ className += " presentationView-selected";
+ }
+ return (
+ <div className={className} key={document[Id] + index} onClick={e => { this.props.gotoDocument(index); e.stopPropagation(); }}>
+ <strong className="presentationView-name">
+ {`${index + 1}. ${title}`}
+ </strong>
+ <button className="presentation-icon" onClick={e => { this.props.deleteDocument(index); e.stopPropagation(); }}>X</button>
+ </div>
+ );
+
+ }
+
+ render() {
+ const children = DocListCast(this.props.Document.data);
+
+ return (
+ <div className="presentationView-listCont">
+ {children.map(this.renderChild)}
+ </div>
+ );
+ }
+}
+
+
+@observer
+export class PresentationView extends React.Component<PresViewProps> {
+ public static Instance: PresentationView;
+
+ //observable means render is re-called every time variable is changed
+ @observable
+ collapsed: boolean = false;
+ closePresentation = action(() => this.props.Document.width = 0);
+ next = () => {
+ const current = NumCast(this.props.Document.selectedDoc);
+ this.gotoDocument(current + 1);
+
+ }
+ back = () => {
+ const current = NumCast(this.props.Document.selectedDoc);
+ this.gotoDocument(current - 1);
+ }
+
+ @action
+ public RemoveDoc = (index: number) => {
+ const value = FieldValue(Cast(this.props.Document.data, listSpec(Doc)));
+ if (value) {
+ value.splice(index, 1);
+ }
+ }
+
+ public gotoDocument = async (index: number) => {
+ const list = FieldValue(Cast(this.props.Document.data, listSpec(Doc)));
+ if (!list) {
+ return;
+ }
+ if (index < 0 || index >= list.length) {
+ return;
+ }
+
+ this.props.Document.selectedDoc = index;
+ const doc = await list[index];
+ DocumentManager.Instance.jumpToDocument(doc);
+ }
+
+ //initilize class variables
+ constructor(props: PresViewProps) {
+ super(props);
+ PresentationView.Instance = this;
+ }
+
+ /**
+ * Adds a document to the presentation view
+ **/
+ @action
+ public PinDoc(doc: Doc) {
+ //add this new doc to props.Document
+ const data = Cast(this.props.Document.data, listSpec(Doc));
+ if (data) {
+ data.push(doc);
+ } else {
+ this.props.Document.data = new List([doc]);
+ }
+
+ this.props.Document.width = 300;
+ }
+
+ render() {
+ let titleStr = StrCast(this.props.Document.title);
+ let width = NumCast(this.props.Document.width);
+
+ //TODO: next and back should be icons
+ return (
+ <div className="presentationView-cont" style={{ width: width, overflow: "hidden" }}>
+ <div className="presentationView-heading">
+ <div className="presentationView-title">{titleStr}</div>
+ <button className='presentation-icon' onClick={this.closePresentation}>X</button>
+ </div>
+ <div className="presentation-buttons">
+ <button className="presentation-button" onClick={this.back}>back</button>
+ <button className="presentation-button" onClick={this.next}>next</button>
+ </div>
+ <PresentationViewList Document={this.props.Document} deleteDocument={this.RemoveDoc} gotoDocument={this.gotoDocument} />
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss
new file mode 100644
index 000000000..20f9b9a49
--- /dev/null
+++ b/src/client/views/PreviewCursor.scss
@@ -0,0 +1,9 @@
+
+.previewCursor {
+ color: black;
+ position: absolute;
+ transform-origin: left top;
+ top: 0;
+ left:0;
+ pointer-events: none;
+} \ No newline at end of file
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
new file mode 100644
index 000000000..7c1d00eb0
--- /dev/null
+++ b/src/client/views/PreviewCursor.tsx
@@ -0,0 +1,64 @@
+import { action, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import "normalize.css";
+import * as React from 'react';
+import "./PreviewCursor.scss";
+
+@observer
+export class PreviewCursor extends React.Component<{}> {
+ private _prompt = React.createRef<HTMLDivElement>();
+ static _onKeyPress?: (e: KeyboardEvent) => void;
+ @observable static _clickPoint = [0, 0];
+ @observable public static Visible = false;
+ //when focus is lost, this will remove the preview cursor
+ @action onBlur = (): void => {
+ PreviewCursor.Visible = false;
+ }
+
+ constructor(props: any) {
+ super(props);
+ document.addEventListener("keydown", this.onKeyPress);
+ document.addEventListener("paste", this.paste);
+ }
+ paste = (e: ClipboardEvent) => {
+ console.log(e.clipboardData);
+ if (e.clipboardData) {
+ console.log(e.clipboardData.getData("text/html"));
+ console.log(e.clipboardData.getData("text/csv"));
+ console.log(e.clipboardData.getData("text/plain"));
+ }
+ }
+
+ @action
+ onKeyPress = (e: KeyboardEvent) => {
+ // Mixing events between React and Native is finicky. In FormattedTextBox, we set the
+ // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
+ // the keyPress here.
+ //if not these keys, make a textbox if preview cursor is active!
+ if (e.key.startsWith("F") && !e.key.endsWith("F")) {
+ } else if (e.key !== "Escape" && e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+ if ((!e.ctrlKey && !e.metaKey) || (e.key >= "a" && e.key <= "z")) {
+ PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
+ PreviewCursor.Visible = false;
+ }
+ }
+ }
+ @action
+ public static Show(x: number, y: number, onKeyPress: (e: KeyboardEvent) => void) {
+ this._clickPoint = [x, y];
+ this._onKeyPress = onKeyPress;
+ setTimeout(action(() => this.Visible = true), (1));
+ }
+ render() {
+ if (!PreviewCursor._clickPoint) {
+ return (null);
+ }
+ if (PreviewCursor.Visible && this._prompt.current) {
+ this._prompt.current.focus();
+ }
+ return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._prompt}
+ style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}>
+ I
+ </div >;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/SearchBox.scss b/src/client/views/SearchBox.scss
new file mode 100644
index 000000000..b38e6091d
--- /dev/null
+++ b/src/client/views/SearchBox.scss
@@ -0,0 +1,102 @@
+@import "globalCssVariables";
+
+.searchBox-bar {
+ height: 32px;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ padding-left: 2px;
+ padding-right: 2px;
+
+ .searchBox-input {
+ width: 130px;
+ -webkit-transition: width 0.4s;
+ transition: width 0.4s;
+ align-self: stretch;
+ }
+
+ .searchBox-input:focus {
+ width: 500px;
+ outline: 3px solid lightblue;
+ }
+
+ .searchBox-barChild {
+ flex: 0 1 auto;
+ margin-left: 2px;
+ margin-right: 2px;
+ }
+
+ .searchBox-filter {
+ align-self: stretch;
+ }
+
+ .searchBox-submit {
+ color: $dark-color;
+ }
+
+ .searchBox-submit:hover {
+ color: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+}
+
+.searchBox-results {
+ margin-left: 27px; //Is there a better way to do this?
+}
+
+.filter-form {
+ background: $dark-color;
+ height: 400px;
+ width: 400px;
+ position: relative;
+ right: 1px;
+ color: $light-color;
+ padding: 10px;
+ flex-direction: column;
+}
+
+#header {
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 100%;
+ height: 40px;
+}
+
+#option {
+ height: 20px;
+}
+
+.searchBox-results {
+ top: 300px;
+ display: flex;
+ flex-direction: column;
+
+ .search-item {
+ width: 500px;
+ height: 50px;
+ background: $light-color-secondary;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ transition: all 0.1s;
+ border-width: 0.11px;
+ border-style: none;
+ border-color: $intermediate-color;
+ border-bottom-style: solid;
+ padding: 10px;
+ white-space: nowrap;
+ font-size: 13px;
+ }
+
+ .search-item:hover {
+ transition: all 0.1s;
+ background: $lighter-alt-accent;
+ }
+
+ .search-title {
+ text-transform: uppercase;
+ text-align: left;
+ width: 8vw;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
new file mode 100644
index 000000000..6e64e1af1
--- /dev/null
+++ b/src/client/views/SearchBox.tsx
@@ -0,0 +1,178 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { observable, action, runInAction } from 'mobx';
+import { Utils } from '../../Utils';
+import { MessageStore } from '../../server/Message';
+import "./SearchBox.scss";
+import { faSearch, faObjectGroup } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+// const app = express();
+// import * as express from 'express';
+import { Search } from '../../server/Search';
+import * as rp from 'request-promise';
+import { SearchItem } from './SearchItem';
+import { isString } from 'util';
+import { constant } from 'async';
+import { DocServer } from '../DocServer';
+import { Doc } from '../../new_fields/Doc';
+import { Id } from '../../new_fields/RefField';
+import { DocumentManager } from '../util/DocumentManager';
+import { SetupDrag } from '../util/DragManager';
+import { Docs } from '../documents/Documents';
+
+library.add(faSearch);
+library.add(faObjectGroup);
+
+@observer
+export class SearchBox extends React.Component {
+ @observable
+ searchString: string = "";
+
+ @observable private _open: boolean = false;
+ @observable private _resultsOpen: boolean = false;
+
+ @observable
+ private _results: Doc[] = [];
+
+ @action.bound
+ onChange(e: React.ChangeEvent<HTMLInputElement>) {
+ this.searchString = e.target.value;
+ }
+
+ @action
+ submitSearch = async () => {
+ let query = this.searchString;
+ //gets json result into a list of documents that can be used
+ const results = await this.getResults(query);
+
+ runInAction(() => {
+ this._resultsOpen = true;
+ this._results = results;
+ });
+ }
+
+ @action
+ getResults = async (query: string) => {
+ let response = await rp.get(DocServer.prepend('/search'), {
+ qs: {
+ query
+ }
+ });
+ let res: string[] = JSON.parse(response);
+ const fields = await DocServer.GetRefFields(res);
+ const docs: Doc[] = [];
+ for (const id of res) {
+ const field = fields[id];
+ if (field instanceof Doc) {
+ docs.push(field);
+ }
+ }
+ return docs;
+ }
+
+ @action
+ handleClickFilter = (e: Event): void => {
+ var className = (e.target as any).className;
+ var id = (e.target as any).id;
+ if (className !== "filter-button" && className !== "filter-form") {
+ this._open = false;
+ }
+
+ }
+
+ @action
+ handleClickResults = (e: Event): void => {
+ var className = (e.target as any).className;
+ var id = (e.target as any).id;
+ if (id !== "result") {
+ this._resultsOpen = false;
+ this._results = [];
+ }
+
+ }
+
+ componentWillMount() {
+ document.addEventListener('mousedown', this.handleClickFilter, false);
+ document.addEventListener('mousedown', this.handleClickResults, false);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('mousedown', this.handleClickFilter, false);
+ document.removeEventListener('mousedown', this.handleClickResults, false);
+ }
+
+ @action
+ toggleFilterDisplay = () => {
+ this._open = !this._open;
+ }
+
+ enter = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === "Enter") {
+ this.submitSearch();
+ }
+ }
+
+ collectionRef = React.createRef<HTMLSpanElement>();
+ startDragCollection = async () => {
+ const results = await this.getResults(this.searchString);
+ const docs = results.map(doc => {
+ const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
+ if (isProto) {
+ return Doc.MakeDelegate(doc);
+ } else {
+ return Doc.MakeAlias(doc);
+ }
+ });
+ let x = 0;
+ let y = 0;
+ for (const doc of docs) {
+ doc.x = x;
+ doc.y = y;
+ doc.width = 200;
+ doc.height = 200;
+ x += 250;
+ if (x > 1000) {
+ x = 0;
+ y += 250;
+ }
+ }
+ return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, title: `Search Docs: "${this.searchString}"` });
+ }
+
+ // Useful queries:
+ // Delegates of a document: {!join from=id to=proto_i}id:{protoId}
+ // Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId}
+ render() {
+ return (
+ <div>
+ <div className="searchBox-container">
+ <div className="searchBox-bar">
+ <span onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef}>
+ <FontAwesomeIcon icon="object-group" className="searchBox-barChild" size="lg" />
+ </span>
+ <input value={this.searchString} onChange={this.onChange} type="text" placeholder="Search..."
+ className="searchBox-barChild searchBox-input" onKeyPress={this.enter}
+ style={{ width: this._resultsOpen ? "500px" : undefined }} />
+ <button className="searchBox-barChild searchBox-filter" onClick={this.toggleFilterDisplay}>Filter</button>
+ <FontAwesomeIcon icon="search" size="lg" className="searchBox-barChild searchBox-submit" />
+ </div>
+ {this._resultsOpen ? (
+ <div className="searchBox-results">
+ {this._results.map(result => <SearchItem doc={result} key={result[Id]} />)}
+ </div>
+ ) : null}
+ </div>
+ {this._open ? (
+ <div className="filter-form" id="filter" style={this._open ? { display: "flex" } : { display: "none" }}>
+ <div className="filter-form" id="header">Filter Search Results</div>
+ <div className="filter-form" id="option">
+ filter by collection, key, type of node
+ </div>
+
+ </div>
+ ) : null}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
new file mode 100644
index 000000000..01c7316d6
--- /dev/null
+++ b/src/client/views/SearchItem.tsx
@@ -0,0 +1,73 @@
+import React = require("react");
+import { Doc } from "../../new_fields/Doc";
+import { DocumentManager } from "../util/DocumentManager";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Cast } from "../../new_fields/Types";
+import { FieldView, FieldViewProps } from './nodes/FieldView';
+import { computed } from "mobx";
+import { IconField } from "../../new_fields/IconField";
+import { SetupDrag } from "../util/DragManager";
+
+
+export interface SearchProps {
+ doc: Doc;
+}
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+export class SearchItem extends React.Component<SearchProps> {
+
+ onClick = () => {
+ DocumentManager.Instance.jumpToDocument(this.props.doc);
+ }
+
+ //needs help
+ // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
+
+
+ public static DocumentIcon(layout: string) {
+ let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+ layout.indexOf("ImageBox") !== -1 ? faImage :
+ layout.indexOf("Formatted") !== -1 ? faStickyNote :
+ layout.indexOf("Video") !== -1 ? faFilm :
+ layout.indexOf("Collection") !== -1 ? faObjectGroup :
+ faCaretUp;
+ return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
+ }
+ onPointerEnter = (e: React.PointerEvent) => {
+ this.props.doc.libraryBrush = true;
+ Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
+ }
+ onPointerLeave = (e: React.PointerEvent) => {
+ this.props.doc.libraryBrush = false;
+ Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
+ }
+
+ collectionRef = React.createRef<HTMLDivElement>();
+ startDocDrag = () => {
+ let doc = this.props.doc;
+ const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
+ if (isProto) {
+ return Doc.MakeDelegate(doc);
+ } else {
+ return Doc.MakeAlias(doc);
+ }
+ }
+ render() {
+ return (
+ <div className="search-item" ref={this.collectionRef} id="result"
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+ onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
+ <div className="search-title" id="result" >title: {this.props.doc.title}</div>
+ {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
+ {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
new file mode 100644
index 000000000..e5b679e24
--- /dev/null
+++ b/src/client/views/TemplateMenu.tsx
@@ -0,0 +1,90 @@
+import { observable, computed, action, trace } from "mobx";
+import React = require("react");
+import { observer } from "mobx-react";
+import './DocumentDecorations.scss';
+import { Template } from "./Templates";
+import { DocumentView } from "./nodes/DocumentView";
+import { List } from "../../new_fields/List";
+import { Doc } from "../../new_fields/Doc";
+import { NumCast } from "../../new_fields/Types";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+@observer
+class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
+ render() {
+ if (this.props.template) {
+ return (
+ <li className="templateToggle">
+ <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event, this.props.template)} />
+ {this.props.template.Name}
+ </li>
+ );
+ } else {
+ return (null);
+ }
+ }
+}
+
+export interface TemplateMenuProps {
+ docs: DocumentView[];
+ templates: Map<Template, boolean>;
+}
+
+@observer
+export class TemplateMenu extends React.Component<TemplateMenuProps> {
+ @observable private _hidden: boolean = true;
+
+ constructor(props: TemplateMenuProps) {
+ super(props);
+ }
+
+ @action
+ toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
+ if (event.target.checked) {
+ if (template.Name === "Bullet") {
+ let topDocView = this.props.docs[0];
+ topDocView.addTemplate(template);
+ topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
+ } else {
+ this.props.docs.map(d => d.addTemplate(template));
+ }
+ this.props.templates.set(template, true);
+ } else {
+ if (template.Name === "Bullet") {
+ let topDocView = this.props.docs[0];
+ topDocView.removeTemplate(template);
+ topDocView.props.Document.subBulletDocs = undefined;
+ } else {
+ this.props.docs.map(d => d.removeTemplate(template));
+ }
+ this.props.templates.set(template, false);
+ }
+ }
+
+ @action
+ componentWillReceiveProps(nextProps: TemplateMenuProps) {
+ // this._templates = nextProps.templates;
+ }
+
+ @action
+ toggleTemplateActivity = (): void => {
+ this._hidden = !this._hidden;
+ }
+
+ render() {
+ let templateMenu: Array<JSX.Element> = [];
+ this.props.templates.forEach((checked, template) =>
+ templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
+
+ return (
+ <div className="templating-menu" >
+ <div className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
+ <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+ {templateMenu}
+ </ul>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
new file mode 100644
index 000000000..5a99b3d90
--- /dev/null
+++ b/src/client/views/Templates.tsx
@@ -0,0 +1,92 @@
+import React = require("react");
+
+export enum TemplatePosition {
+ InnerTop,
+ InnerBottom,
+ InnerRight,
+ InnerLeft,
+ TopRight,
+ OutterTop,
+ OutterBottom,
+ OutterRight,
+ OutterLeft,
+}
+
+export class Template {
+ constructor(name: string, position: TemplatePosition, layout: string) {
+ this._name = name;
+ this._position = position;
+ this._layout = layout;
+ }
+
+ private _name: string;
+ private _position: TemplatePosition;
+ private _layout: string;
+
+ get Name(): string {
+ return this._name;
+ }
+
+ get Position(): TemplatePosition {
+ return this._position;
+ }
+
+ get Layout(): string {
+ return this._layout;
+ }
+}
+
+export namespace Templates {
+ // export const BasicLayout = new Template("Basic layout", "{layout}");
+
+ export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
+ `<div>
+ <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+ <div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute">
+ <FormattedTextBox {...props} fieldKey={"caption"} />
+ </div>
+ </div>` );
+
+ export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
+ `<div>
+ <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+ <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
+ <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
+ </div>
+ </div>` );
+
+ export const Title = new Template("Title", TemplatePosition.InnerTop,
+ `<div><div style="height:calc(100% - 25px); margin-top: 25px; width:100%;position:absolute;">{layout}</div>
+ <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
+ <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
+ </div></div>` );
+
+ export const Bullet = new Template("Bullet", TemplatePosition.InnerTop,
+ `<div>
+ <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+ <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
+ <img id="isExpander" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg=="
+ width="15px" height="15px"/>
+ </div>
+ </div>`
+ );
+
+ export function ImageOverlay(width: number, height: number, field: string = "thumbnail") {
+ return (`<div>
+ <div style="height:100%; width:100%; position:absolute;">{layout}</div>
+ <div style="height:auto; width:${width}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
+ <ImageBox id="isExpander" {...props} style="width:100%; height=auto;" PanelWidth={${width}} fieldKey={"${field}"} />
+ </div>
+ </div>`);
+ }
+
+ export const TemplateList: Template[] = [Title, TitleOverlay, Caption, Bullet];
+
+ export function sortTemplates(a: Template, b: Template) {
+ if (a.Position < b.Position) { return -1; }
+ if (a.Position > b.Position) { return 1; }
+ return 0;
+ }
+
+}
+
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
new file mode 100644
index 000000000..6f97e60f8
--- /dev/null
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -0,0 +1,23 @@
+// this file is for overriding all the css from installed node modules
+
+// goldenlayout stuff
+div .lm_header {
+ background: $dark-color;
+ min-height: 2em;
+}
+
+.lm_tab {
+ margin-top: 0.6em !important;
+ padding-top: 0.5em !important;
+ min-height: 1.35em;
+ padding-bottom: 0px;
+ border-radius: 5px;
+ font-family: $sans-serif !important;
+}
+
+.lm_header .lm_controls {
+ right: 1em !important;
+}
+
+// @TODO the ril__navgiation buttons in the img gallery are a lil messed up but I can't figure out
+// why. Low priority for now but it's bugging me. --Julie
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
new file mode 100644
index 000000000..31bdf213e
--- /dev/null
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -0,0 +1,176 @@
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { ContextMenu } from '../ContextMenu';
+import { FieldViewProps } from '../nodes/FieldView';
+import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Types';
+import { Doc, FieldResult, Opt, DocListCast } from '../../../new_fields/Doc';
+import { listSpec } from '../../../new_fields/Schema';
+import { List } from '../../../new_fields/List';
+import { Id } from '../../../new_fields/RefField';
+import { SelectionManager } from '../../util/SelectionManager';
+
+export enum CollectionViewType {
+ Invalid,
+ Freeform,
+ Schema,
+ Docking,
+ Tree,
+}
+
+export interface CollectionRenderProps {
+ addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+ removeDocument: (document: Doc) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ active: () => boolean;
+ whenActiveChanged: (isActive: boolean) => void;
+}
+
+export interface CollectionViewProps extends FieldViewProps {
+ onContextMenu?: (e: React.MouseEvent) => void;
+ children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null;
+ className?: string;
+ contentRef?: React.Ref<HTMLDivElement>;
+}
+
+
+@observer
+export class CollectionBaseView extends React.Component<CollectionViewProps> {
+ get collectionViewType(): CollectionViewType | undefined {
+ let Document = this.props.Document;
+ let viewField = Cast(Document.viewType, "number");
+ if (viewField !== undefined) {
+ return viewField;
+ } else {
+ return CollectionViewType.Invalid;
+ }
+ }
+
+ active = (): boolean => {
+ var isSelected = this.props.isSelected();
+ var topMost = this.props.isTopMost;
+ return isSelected || this._isChildActive || topMost;
+ }
+
+ //TODO should this be observable?
+ private _isChildActive = false;
+ whenActiveChanged = (isActive: boolean) => {
+ this._isChildActive = isActive;
+ this.props.whenActiveChanged(isActive);
+ }
+
+ createsCycle(documentToAdd: Doc, containerDocument: Doc): boolean {
+ if (!(documentToAdd instanceof Doc)) {
+ return false;
+ }
+ let data = DocListCast(documentToAdd.data);
+ for (const doc of data) {
+ if (this.createsCycle(doc, containerDocument)) {
+ return true;
+ }
+ }
+ let annots = DocListCast(documentToAdd.annotations);
+ for (const annot of annots) {
+ if (this.createsCycle(annot, containerDocument)) {
+ return true;
+ }
+ }
+ for (let containerProto: Opt<Doc> = containerDocument; containerProto !== undefined; containerProto = FieldValue(containerProto.proto)) {
+ if (containerProto[Id] === documentToAdd[Id]) {
+ return true;
+ }
+ }
+ return false;
+ }
+ @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
+
+ @action.bound
+ addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
+ let props = this.props;
+ var curPage = NumCast(props.Document.curPage, -1);
+ Doc.SetOnPrototype(doc, "page", curPage);
+ if (curPage >= 0) {
+ Doc.SetOnPrototype(doc, "annotationOn", props.Document);
+ }
+ if (!this.createsCycle(doc, props.Document)) {
+ //TODO This won't create the field if it doesn't already exist
+ const value = Cast(props.Document[props.fieldKey], listSpec(Doc));
+ let alreadyAdded = true;
+ if (value !== undefined) {
+ if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
+ alreadyAdded = false;
+ value.push(doc);
+ }
+ } else {
+ alreadyAdded = false;
+ Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new List([doc]));
+ }
+ // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
+ if (!alreadyAdded && (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid)) {
+ let zoom = NumCast(this.props.Document.scale, 1);
+ Doc.SetOnPrototype(doc, "zoomBasis", zoom);
+ }
+ }
+ return true;
+ }
+
+ @action.bound
+ removeDocument(doc: Doc): boolean {
+ const props = this.props;
+ //TODO This won't create the field if it doesn't already exist
+ const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
+ let index = -1;
+ for (let i = 0; i < value.length; i++) {
+ let v = value[i];
+ if (v instanceof Doc && v[Id] === doc[Id]) {
+ index = i;
+ break;
+ }
+ }
+ PromiseValue(Cast(doc.annotationOn, Doc)).then((annotationOn) => {
+ if (annotationOn === props.Document) {
+ doc.annotationOn = undefined;
+ }
+ });
+
+ if (index !== -1) {
+ value.splice(index, 1);
+
+ // SelectionManager.DeselectAll()
+ ContextMenu.Instance.clearItems();
+ return true;
+ }
+ return false;
+ }
+
+ @action.bound
+ moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
+ if (this.props.Document === targetCollection) {
+ return true;
+ }
+ if (this.removeDocument(doc)) {
+ SelectionManager.DeselectAll();
+ return addDocument(doc);
+ }
+ return false;
+ }
+
+ render() {
+ const props: CollectionRenderProps = {
+ addDocument: this.addDocument,
+ removeDocument: this.removeDocument,
+ moveDocument: this.moveDocument,
+ active: this.active,
+ whenActiveChanged: this.whenActiveChanged,
+ };
+ const viewtype = this.collectionViewType;
+ return (
+ <div className={this.props.className || "collectionView-cont"}
+ style={{ borderRadius: "inherit", pointerEvents: "all" }}
+ onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
+ {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 2706c3272..0e7e0afa7 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,9 +1,30 @@
+@import "../../views/globalCssVariables.scss";
+
.collectiondockingview-content {
height: 100%;
}
+.lm_active .messageCounter{
+ color:white;
+ background: #999999;
+}
+.messageCounter {
+ width:18px;
+ height:20px;
+ text-align: center;
+ border-radius: 20px;
+ margin-left: 5px;
+ transform: translate(0px, -8px);
+ display: inline-block;
+ background: transparent;
+ border: 1px #999999 solid;
+}
.collectiondockingview-container {
- position: relative;
+ width: 100%;
+ height: 100%;
+ border-style: solid;
+ border-width: $COLLECTION_BORDER_WIDTH;
+ position: absolute;
top: 0;
left: 0;
overflow: hidden;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6a0404663..28624e020 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,41 +1,46 @@
-import * as GoldenLayout from "golden-layout";
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction } from "mobx";
+import { action, observable, reaction, Lambda } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
-import { Document } from "../../../fields/Document";
-import { KeyStore } from "../../../fields/KeyStore";
import Measure from "react-measure";
-import { FieldId, Opt, Field } from "../../../fields/Field";
-import { Utils } from "../../../Utils";
-import { Server } from "../../Server";
-import { undoBatch } from "../../util/UndoManager";
+import * as GoldenLayout from "../../../client/goldenLayout";
+import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
+import { FieldId, Id } from "../../../new_fields/RefField";
+import { listSpec } from "../../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
+import { Transform } from '../../util/Transform';
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
import "./CollectionDockingView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { SubCollectionViewProps } from "./CollectionSubView";
import React = require("react");
-import { SubCollectionViewProps } from "./CollectionViewBase";
+import { ParentDocSelector } from './ParentDocumentSelector';
+import { DocumentManager } from '../../util/DocumentManager';
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
public static Instance: CollectionDockingView;
- public static makeDocumentConfig(document: Document) {
+ public static makeDocumentConfig(document: Doc, width?: number) {
return {
type: 'react-component',
component: 'DocumentFrameRenderer',
- title: document.Title,
+ title: document.title,
+ width: width,
props: {
- documentId: document.Id,
+ documentId: document[Id],
//collectionDockingView: CollectionDockingView.Instance
}
- }
+ };
}
private _goldenLayout: any = null;
private _containerRef = React.createRef<HTMLDivElement>();
- private _fullScreen: any = null;
private _flush: boolean = false;
+ private _ignoreStateChange = "";
constructor(props: SubCollectionViewProps) {
super(props);
@@ -43,43 +48,84 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
}
- public StartOtherDrag(dragDoc: Document, e: any) {
- this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 })
+ hack: boolean = false;
+ undohack: any = null;
+ public StartOtherDrag(dragDocs: Doc[], e: any) {
+ this.hack = true;
+ this.undohack = UndoManager.StartBatch("goldenDrag");
+ dragDocs.map(dragDoc =>
+ this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
+ onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
}
@action
- public OpenFullScreen(document: Document) {
+ public OpenFullScreen(document: Doc) {
let newItemStackConfig = {
type: 'stack',
content: [CollectionDockingView.makeDocumentConfig(document)]
- }
+ };
var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
this._goldenLayout.root.contentItems[0].addChild(docconfig);
docconfig.callDownwards('_$init');
this._goldenLayout._$maximiseItem(docconfig);
- this._fullScreen = docconfig;
+ this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
this.stateChanged();
}
+
+ @undoBatch
@action
- public CloseFullScreen() {
- if (this._fullScreen) {
- this._goldenLayout._$minimiseItem(this._fullScreen);
- this._goldenLayout.root.contentItems[0].removeChild(this._fullScreen);
- this._fullScreen = null;
+ public CloseRightSplit(document: Doc): boolean {
+ let retVal = false;
+ if (this._goldenLayout.root.contentItems[0].isRow) {
+ retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
+ if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
+ Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
+ child.contentItems[0].remove();
+ this.layoutChanged(document);
+ return true;
+ } else {
+ Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
+ if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
+ child.contentItems[j].remove();
+ child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
+ let docs = Cast(this.props.Document.data, listSpec(Doc));
+ docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
+ return true;
+ }
+ return false;
+ });
+ }
+ return false;
+ });
+ }
+ if (retVal) {
this.stateChanged();
}
+ return retVal;
+ }
+
+ @action
+ layoutChanged(removed?: Doc) {
+ this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
+ this._goldenLayout.emit('stateChanged');
+ this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+ if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
+ this.stateChanged();
}
//
// Creates a vertical split on the right side of the docking view, and then adds the Document to that split
//
@action
- public AddRightSplit(document: Document, minimize: boolean = false) {
- this._goldenLayout.emit('stateChanged');
+ public AddRightSplit(document: Doc, minimize: boolean = false) {
+ let docs = Cast(this.props.Document.data, listSpec(Doc));
+ if (docs) {
+ docs.push(document);
+ }
let newItemStackConfig = {
type: 'stack',
content: [CollectionDockingView.makeDocumentConfig(document)]
- }
+ };
var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
@@ -94,32 +140,45 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
newRow.addChild(newContentItem, undefined, true);
newRow.addChild(collayout, 0, true);
- collayout.config["width"] = 50;
- newContentItem.config["width"] = 50;
+ collayout.config.width = 50;
+ newContentItem.config.width = 50;
}
if (minimize) {
- newContentItem.config["width"] = 10;
- newContentItem.config["height"] = 10;
+ // bcz: this makes the drag image show up better, but it also messes with fixed layout sizes
+ // newContentItem.config.width = 10;
+ // newContentItem.config.height = 10;
}
newContentItem.callDownwards('_$init');
- this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
- this._goldenLayout.emit('stateChanged');
- this.stateChanged();
+ this.layoutChanged();
+
return newContentItem;
}
+ @action
+ public AddTab(stack: any, document: Doc) {
+ let docs = Cast(this.props.Document.data, listSpec(Doc));
+ if (docs) {
+ docs.push(document);
+ }
+ let docContentConfig = CollectionDockingView.makeDocumentConfig(document);
+ var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout);
+ stack.addChild(newContentItem.contentItems[0], undefined);
+ this.layoutChanged();
+ }
setupGoldenLayout() {
- var config = this.props.Document.GetText(KeyStore.Data, "");
+ var config = StrCast(this.props.Document.dockingConfig);
if (config) {
if (!this._goldenLayout) {
this._goldenLayout = new GoldenLayout(JSON.parse(config));
}
else {
- if (config == JSON.stringify(this._goldenLayout.toConfig()))
+ if (config === JSON.stringify(this._goldenLayout.toConfig())) {
return;
+ }
try {
this._goldenLayout.unbind('itemDropped', this.itemDropped);
this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
this._goldenLayout.unbind('stackCreated', this.stackCreated);
} catch (e) { }
this._goldenLayout.destroy();
@@ -127,6 +186,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
this._goldenLayout.on('itemDropped', this.itemDropped);
this._goldenLayout.on('tabCreated', this.tabCreated);
+ this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
this._goldenLayout.on('stackCreated', this.stackCreated);
this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
this._goldenLayout.container = this._containerRef.current;
@@ -140,22 +200,39 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this._goldenLayout.init();
}
}
+ reactionDisposer?: Lambda;
componentDidMount: () => void = () => {
if (this._containerRef.current) {
- reaction(
- () => this.props.Document.GetText(KeyStore.Data, ""),
- () => this.setupGoldenLayout(), { fireImmediately: true });
+ this.reactionDisposer = reaction(
+ () => StrCast(this.props.Document.dockingConfig),
+ () => {
+ if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
+ // Because this is in a set timeout, if this component unmounts right after mounting,
+ // we will leak a GoldenLayout, because we try to destroy it before we ever create it
+ setTimeout(() => this.setupGoldenLayout(), 1);
+ }
+ this._ignoreStateChange = "";
+ }, { fireImmediately: true });
window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
}
}
componentWillUnmount: () => void = () => {
- this._goldenLayout.unbind('itemDropped', this.itemDropped);
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
- this._goldenLayout.unbind('stackCreated', this.stackCreated);
- this._goldenLayout.destroy();
+ try {
+ this._goldenLayout.unbind('itemDropped', this.itemDropped);
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+ } catch (e) {
+
+ }
+ if (this._goldenLayout) this._goldenLayout.destroy();
this._goldenLayout = null;
window.removeEventListener('resize', this.onResize);
+
+ if (this.reactionDisposer) {
+ this.reactionDisposer();
+ }
}
@action
onResize = (event: any) => {
@@ -175,7 +252,36 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@action
onPointerDown = (e: React.PointerEvent): void => {
var className = (e.target as any).className;
- if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") {
+ if (className === "messageCounter") {
+ e.stopPropagation();
+ e.preventDefault();
+ let x = e.clientX;
+ let y = e.clientY;
+ let docid = (e.target as any).DashDocId;
+ let tab = (e.target as any).parentElement as HTMLElement;
+ DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
+ (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
+ } else
+ if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ let x = e.clientX;
+ let y = e.clientY;
+ let docid = (e.target as any).DashDocId;
+ let tab = (e.target as any).parentElement as HTMLElement;
+ DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+ if (f instanceof Doc) {
+ DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y,
+ {
+ handlers: {
+ dragComplete: emptyFunction,
+ },
+ hideSource: false
+ });
+ }
+ }));
+ }
+ if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
this._flush = true;
}
if (this.props.active()) {
@@ -185,20 +291,77 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@undoBatch
stateChanged = () => {
+ let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc));
+ CollectionDockingView.Instance._removedDocs.map(theDoc =>
+ docs && docs.indexOf(theDoc) !== -1 &&
+ docs.splice(docs.indexOf(theDoc), 1));
+ CollectionDockingView.Instance._removedDocs.length = 0;
var json = JSON.stringify(this._goldenLayout.toConfig());
- this.props.Document.SetText(KeyStore.Data, json)
+ this.props.Document.dockingConfig = json;
+ if (this.undohack && !this.hack) {
+ this.undohack.end();
+ this.undohack = undefined;
+ }
+ this.hack = false;
}
itemDropped = () => {
this.stateChanged();
}
- tabCreated = (tab: any) => {
+
+ htmlToElement(html: string) {
+ var template = document.createElement('template');
+ html = html.trim(); // Never return a text node of whitespace as the result
+ template.innerHTML = html;
+ return template.content.firstChild;
+ }
+
+ tabCreated = async (tab: any) => {
+ if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
+ if (tab.contentItem.config.fixed) {
+ tab.contentItem.parent.config.fixed = true;
+ }
+ DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => {
+ if (doc instanceof Doc) {
+ let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);
+ tab.element.append(counter);
+ let upDiv = document.createElement("span");
+ ReactDOM.render(<ParentDocSelector Document={doc} />, upDiv);
+ tab.reactComponents = [upDiv];
+ tab.element.append(upDiv);
+ counter.DashDocId = tab.contentItem.config.props.documentId;
+ tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
+ () => {
+ counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
+ tab.titleElement[0].textContent = doc.title;
+ }, { fireImmediately: true });
+ tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
+ }
+ });
+ }
tab.closeElement.off('click') //unbind the current click handler
- .click(function () {
+ .click(async function () {
+ if (tab.reactionDisposer) {
+ tab.reactionDisposer();
+ }
+ let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
+ if (doc instanceof Doc) {
+ let theDoc = doc;
+ CollectionDockingView.Instance._removedDocs.push(theDoc);
+ }
tab.contentItem.remove();
});
}
+ tabDestroyed = (tab: any) => {
+ if (tab.reactComponents) {
+ for (const ele of tab.reactComponents) {
+ ReactDOM.unmountComponentAtNode(ele);
+ }
+ }
+ }
+ _removedDocs: Doc[] = [];
+
stackCreated = (stack: any) => {
//stack.header.controlsContainer.find('.lm_popout').hide();
stack.header.controlsContainer.find('.lm_close') //get the close icon
@@ -206,70 +369,103 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
.click(action(function () {
//if (confirm('really close this?')) {
stack.remove();
+ stack.contentItems.map(async (contentItem: any) => {
+ let doc = await DocServer.GetRefField(contentItem.config.props.documentId);
+ if (doc instanceof Doc) {
+ let theDoc = doc;
+ CollectionDockingView.Instance._removedDocs.push(theDoc);
+ }
+ });
//}
}));
+ stack.header.controlsContainer.find('.lm_popout') //get the close icon
+ .off('click') //unbind the current click handler
+ .click(action(function () {
+ stack.config.fixed = !stack.config.fixed;
+ // var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
+ // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
+ }));
}
render() {
return (
<div className="collectiondockingview-container" id="menuContainer"
- onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef}
- style={{
- width: "100%",
- height: "100%",
- borderStyle: "solid",
- borderWidth: `${COLLECTION_BORDER_WIDTH}px`,
- }} />
+ onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
);
}
+
}
interface DockedFrameProps {
- documentId: FieldId,
+ documentId: FieldId;
//collectionDockingView: CollectionDockingView
}
@observer
export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
-
- private _mainCont = React.createRef<HTMLDivElement>();
+ _mainCont = React.createRef<HTMLDivElement>();
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
- @observable private _document: Opt<Document>;
-
+ @observable private _document: Opt<Doc>;
+ _stack: any;
constructor(props: any) {
super(props);
- Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
+ this._stack = (this.props as any).glContainer.parent.parent;
+ DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
}
- private _nativeWidth = () => { return this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); }
- private _nativeHeight = () => { return this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); }
- private _contentScaling = () => { return this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth); }
+ nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
+ nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight);
+ contentScaling = () => {
+ const nativeH = this.nativeHeight();
+ const nativeW = this.nativeWidth();
+ let wscale = this._panelWidth / nativeW;
+ return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
+ }
ScreenToLocalTransform = () => {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!);
- return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling())
+ if (this._mainCont.current && this._mainCont.current.children) {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement);
+ scale = Utils.GetScreenTransform(this._mainCont.current).scale;
+ return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
+ }
+ return Transform.Identity();
}
+ get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
- render() {
- if (!this._document)
+ addDocTab = (doc: Doc) => {
+ CollectionDockingView.Instance.AddTab(this._stack, doc);
+ }
+ get content() {
+ if (!this._document) {
return (null);
- var content =
- <div className="collectionDockingView-content" ref={this._mainCont}>
- <DocumentView key={this._document.Id} Document={this._document}
- AddDocument={undefined}
- RemoveDocument={undefined}
- ContentScaling={this._contentScaling}
- PanelWidth={this._nativeWidth}
- PanelHeight={this._nativeHeight}
+ }
+ return (
+ <div className="collectionDockingView-content" ref={this._mainCont}
+ style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
+ <DocumentView key={this._document[Id]} Document={this._document}
+ toggleMinimized={emptyFunction}
+ bringToFront={emptyFunction}
+ addDocument={undefined}
+ removeDocument={undefined}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.nativeWidth}
+ PanelHeight={this.nativeHeight}
ScreenToLocalTransform={this.ScreenToLocalTransform}
isTopMost={true}
- SelectOnLoad={false}
- focus={(doc: Document) => { }}
+ selectOnLoad={false}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ addDocTab={this.addDocTab}
ContainingCollectionView={undefined} />
- </div>
+ </div >);
+ }
- return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
- {({ measureRef }) => <div ref={measureRef}> {content} </div>}
- </Measure>
+ render() {
+ let theContent = this.content;
+ return !this._document ? (null) :
+ <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
+ {({ measureRef }) => <div ref={measureRef}> {theContent} </div>}
+ </Measure>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
deleted file mode 100644
index d487cd7ce..000000000
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ /dev/null
@@ -1,75 +0,0 @@
-.collectionfreeformview-container {
-
- .collectionfreeformview > .jsx-parser{
- position:absolute;
- height: 100%;
- width: 100%;
- }
-
- border-style: solid;
- box-sizing: border-box;
- position: relative;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- overflow: hidden;
- .collectionfreeformview {
- position: absolute;
- top: 0;
- left: 0;
- width:100%;
- height: 100%;
- }
-}
-.collectionfreeformview-marquee{
- border-style: dashed;
- box-sizing: border-box;
- position: absolute;
- border-width: 1px;
- border-color: black;
-}
-.collectionfreeformview-overlay {
-
- .collectionfreeformview > .jsx-parser{
- position:absolute;
- height: 100%;
- }
- .formattedTextBox-cont {
- background:yellow;
- }
-
- border-style: solid;
- box-sizing: border-box;
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- overflow: hidden;
- .collectionfreeformview {
- position: absolute;
- top: 0;
- left: 0;
- width:100%;
- height: 100%;
- }
-}
-
-.border {
- border-style: solid;
- box-sizing: border-box;
- width: 100%;
- height: 100%;
-}
-
-//this is an animation for the blinking cursor!
-@keyframes blink {
- 0% {opacity: 0}
- 49%{opacity: 0}
- 50% {opacity: 1}
-}
-
-#prevCursor {
- animation: blink 1s infinite;
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
deleted file mode 100644
index b0cd7e017..000000000
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ /dev/null
@@ -1,394 +0,0 @@
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import { TextField } from "../../../fields/TextField";
-import { Documents } from "../../documents/Documents";
-import { DragManager } from "../../util/DragManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionPDFView } from "../collections/CollectionPDFView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionView } from "../collections/CollectionView";
-import { InkingCanvas } from "../InkingCanvas";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { DocumentView } from "../nodes/DocumentView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import { KeyValueBox } from "../nodes/KeyValueBox";
-import { PDFBox } from "../nodes/PDFBox";
-import { WebBox } from "../nodes/WebBox";
-import "./CollectionFreeFormView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { CollectionViewBase } from "./CollectionViewBase";
-import React = require("react");
-import { SelectionManager } from "../../util/SelectionManager";
-const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
-
-@observer
-export class CollectionFreeFormView extends CollectionViewBase {
- private _canvasRef = React.createRef<HTMLDivElement>();
- @observable
- private _lastX: number = 0;
- @observable
- private _lastY: number = 0;
- private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
-
- @observable
- private _downX: number = 0;
- @observable
- private _downY: number = 0;
-
- //determines whether the blinking cursor for indicating whether a text will be made on key down is visible
- @observable
- private _previewCursorVisible: boolean = false;
-
- @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
- @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
- @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
- @computed get isAnnotationOverlay() { return this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
- @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
- @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
- @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
- @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; } // shift so pan position is at center of window for non-overlay collections
- @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
-
- @undoBatch
- @action
- drop = (e: Event, de: DragManager.DropEvent) => {
- super.drop(e, de);
- const docView: DocumentView = de.data["documentView"];
- let doc: Document = docView ? docView.props.Document : de.data["document"];
- if (doc) {
- let screenX = de.x - (de.data["xOffset"] as number || 0);
- let screenY = de.y - (de.data["yOffset"] as number || 0);
- const [x, y] = this.getTransform().transformPoint(screenX, screenY);
- doc.SetNumber(KeyStore.X, x);
- doc.SetNumber(KeyStore.Y, y);
- this.bringToFront(doc);
- }
- }
-
- @observable
- _marquee = false;
-
- @action
- onPointerDown = (e: React.PointerEvent): void => {
- if (((e.button === 2 && this.props.active()) || !e.defaultPrevented) && !e.shiftKey &&
- (!this.isAnnotationOverlay || this.zoomScaling != 1 || e.button == 0)) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointerup", this.onPointerUp);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
- this._downX = e.pageX;
- this._downY = e.pageY;
- }
- }
-
- @action
- onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- e.stopPropagation();
-
- if (this._marquee) {
- if (!e.shiftKey) {
- SelectionManager.DeselectAll();
- }
- var selectedDocs = this.marqueeSelect();
- selectedDocs.map(s => this.props.CollectionView.SelectedDocs.push(s.Id));
- this._marquee = false;
- }
- else if (!this._marquee && Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) {
- //show preview text cursor on tap
- this._previewCursorVisible = true;
- //select is not already selected
- if (!this.props.isSelected()) {
- this.props.select(false);
- }
- }
-
- }
-
- intersectRect(r1: { left: number, right: number, top: number, bottom: number },
- r2: { left: number, right: number, top: number, bottom: number }) {
- return !(r2.left > r1.right ||
- r2.right < r1.left ||
- r2.top > r1.bottom ||
- r2.bottom < r1.top);
- }
-
- marqueeSelect() {
- this.props.CollectionView.SelectedDocs.length = 0;
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
- let p = this.getTransform().transformPoint(this._downX, this._downY);
- let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- let selRect = { left: p[0], top: p[1], right: p[0] + v[0], bottom: p[1] + v[1] }
-
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
- const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
- let selection: Document[] = [];
- if (lvalue && lvalue != FieldWaiting) {
- lvalue.Data.map(doc => {
- var page = doc.GetNumber(KeyStore.Page, 0);
- if (page == curPage || page == 0) {
- var x = doc.GetNumber(KeyStore.X, 0);
- var y = doc.GetNumber(KeyStore.Y, 0);
- var w = doc.GetNumber(KeyStore.Width, 0);
- var h = doc.GetNumber(KeyStore.Height, 0);
- if (this.intersectRect({ left: x, top: y, right: x + w, bottom: y + h }, selRect))
- selection.push(doc)
- }
- })
- }
- return selection;
- }
-
- @action
- onPointerMove = (e: PointerEvent): void => {
- if (!e.cancelBubble && this.props.active()) {
- e.stopPropagation();
- e.preventDefault();
- let wasMarquee = this._marquee;
- this._marquee = e.buttons != 2;
- if (this._marquee && !wasMarquee) {
- document.addEventListener("keydown", this.marqueeCommand);
- }
-
- if (!this._marquee) {
- let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
- let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
- let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- this._previewCursorVisible = false;
- this.SetPan(x - dx, y - dy);
- }
- }
- this._lastX = e.pageX;
- this._lastY = e.pageY;
- }
-
- @action
- marqueeCommand = (e: KeyboardEvent) => {
- if (e.key == "Backspace") {
- this.marqueeSelect().map(d => this.props.removeDocument(d));
- }
- if (e.key == "c") {
- }
- }
-
- @action
- onPointerWheel = (e: React.WheelEvent): void => {
- e.stopPropagation();
- e.preventDefault();
- let coefficient = 1000;
-
- if (e.ctrlKey) {
- var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
- const coefficient = 1000;
- let deltaScale = (1 - (e.deltaY / coefficient));
- this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale);
- this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale);
- e.stopPropagation();
- e.preventDefault();
- } else {
- // if (modes[e.deltaMode] == 'pixels') coefficient = 50;
- // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height??
- let transform = this.getTransform();
-
- let deltaScale = (1 - (e.deltaY / coefficient));
- if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay)
- deltaScale = 1 / this.zoomScaling;
- let [x, y] = transform.transformPoint(e.clientX, e.clientY);
-
- let localTransform = this.getLocalTransform()
- localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
- // console.log(localTransform)
-
- this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
- this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
- }
- }
-
- @action
- private SetPan(panX: number, panY: number) {
- var x1 = this.getLocalTransform().inverse().Scale;
- var x2 = this.getTransform().inverse().Scale;
- const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX));
- const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY));
- this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
- this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
- }
-
- @action
- onDrop = (e: React.DragEvent): void => {
- var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
- super.onDrop(e, { x: pt[0], y: pt[1] });
- }
-
- onDragOver = (): void => {
- }
-
- @action
- onKeyDown = (e: React.KeyboardEvent<Element>) => {
- //if not these keys, make a textbox if preview cursor is active!
- if (!e.ctrlKey && !e.altKey) {
- if (this._previewCursorVisible) {
- //make textbox and add it to this collection
- let [x, y] = this.getTransform().transformPoint(this._downX, this._downY); (this._downX, this._downY);
- let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" });
- // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
- this._selectOnLoaded = newBox.Id;
- //set text to be the typed key and get focus on text box
- this.props.CollectionView.addDocument(newBox);
- //remove cursor from screen
- this._previewCursorVisible = false;
- }
- }
- }
-
- @action
- bringToFront(doc: Document) {
- const { fieldKey: fieldKey, Document: Document } = this.props;
-
- const value: Document[] = Document.GetList<Document>(fieldKey, []).slice();
- value.sort((doc1, doc2) => {
- if (doc1 === doc) {
- return 1;
- }
- if (doc2 === doc) {
- return -1;
- }
- return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
- }).map((doc, index) => {
- doc.SetNumber(KeyStore.ZIndex, index + 1)
- });
- }
-
- @computed get backgroundLayout(): string | undefined {
- let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
- if (field && field !== "<Waiting>") {
- return field.Data;
- }
- }
- @computed get overlayLayout(): string | undefined {
- let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField);
- if (field && field !== "<Waiting>") {
- return field.Data;
- }
- }
-
- focusDocument = (doc: Document) => {
- let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2;
- let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2;
- this.SetPan(x, y);
- this.props.focus(this.props.Document);
- }
-
-
- @computed
- get views() {
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
- const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
- if (lvalue && lvalue != FieldWaiting) {
- return lvalue.Data.map(doc => {
- var page = doc.GetNumber(KeyStore.Page, 0);
- return (page != curPage && page != 0) ? (null) :
- (<CollectionFreeFormDocumentView key={doc.Id} Document={doc}
- AddDocument={this.props.addDocument}
- RemoveDocument={this.props.removeDocument}
- ScreenToLocalTransform={this.getTransform}
- isTopMost={false}
- SelectOnLoad={doc.Id === this._selectOnLoaded}
- ContentScaling={this.noScaling}
- PanelWidth={doc.Width}
- PanelHeight={doc.Height}
- ContainingCollectionView={this.props.CollectionView}
- focus={this.focusDocument}
- />);
- })
- }
- return null;
- }
-
- @computed
- get backgroundView() {
- return !this.backgroundLayout ? (null) :
- (<JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }}
- bindings={this.props.bindings}
- jsx={this.backgroundLayout}
- showWarnings={true}
- onError={(test: any) => console.log(test)}
- />);
- }
- @computed
- get overlayView() {
- return !this.overlayLayout ? (null) :
- (<JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }}
- bindings={this.props.bindings}
- jsx={this.overlayLayout}
- showWarnings={true}
- onError={(test: any) => console.log(test)}
- />);
- }
-
- getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
- getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
- noScaling = () => 1;
-
- //when focus is lost, this will remove the preview cursor
- @action
- onBlur = (e: React.FocusEvent<HTMLDivElement>): void => {
- this._previewCursorVisible = false;
- }
-
- render() {
- //determines whether preview text cursor should be visible (ie when user taps this collection it should)
- let cursor = null;
- if (this._previewCursorVisible) {
- //get local position and place cursor there!
- let [x, y] = this.getTransform().transformPoint(this._downX, this._downY);
- cursor = <div id="prevCursor" onKeyPress={this.onKeyDown} style={{ color: "black", position: "absolute", transformOrigin: "left top", transform: `translate(${x}px, ${y}px)` }}>I</div>
- }
-
- let p = this.getTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
- let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- var marquee = this._marquee ? <div className="collectionfreeformview-marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }}></div> : (null);
-
- let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
-
- const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
- const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
-
- return (
- <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
- onPointerDown={this.onPointerDown}
- onKeyPress={this.onKeyDown}
- onWheel={this.onPointerWheel}
- onDrop={this.onDrop.bind(this)}
- onDragOver={this.onDragOver}
- onBlur={this.onBlur}
- style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px`, }}
- tabIndex={0}
- ref={this.createDropTarget}>
- <div className="collectionfreeformview"
- style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }}
- ref={this._canvasRef}>
- {this.backgroundView}
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
- {cursor}
- {this.views}
- {marquee}
- </div>
- {this.overlayView}
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
new file mode 100644
index 000000000..f6fb79582
--- /dev/null
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -0,0 +1,49 @@
+.collectionPdfView-buttonTray {
+ top : 15px;
+ left : 20px;
+ position: relative;
+ transform-origin: left top;
+ position: absolute;
+}
+.collectionPdfView-thumb {
+ width:25px;
+ height:25px;
+ transform-origin: left top;
+ position: absolute;
+ background: darkgray;
+}
+.collectionPdfView-slider {
+ width:25px;
+ height:25px;
+ transform-origin: left top;
+ position: absolute;
+ background: lightgray;
+}
+.collectionPdfView-cont{
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left:0;
+}
+.collectionPdfView-cont-dragging {
+ span {
+ user-select: none;
+ }
+}
+.collectionPdfView-backward {
+ color : white;
+ font-size: 24px;
+ top :0px;
+ left : 0px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+}
+.collectionPdfView-forward {
+ color : white;
+ font-size: 24px;
+ top :0px;
+ left : 45px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index bcb1cd2f7..a6614da21 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,57 +1,85 @@
-import { action, computed } from "mobx";
+import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { KeyStore } from "../../../fields/KeyStore";
import { ContextMenu } from "../ContextMenu";
-import { CollectionView, CollectionViewType } from "./CollectionView";
-import { CollectionViewProps } from "./CollectionViewBase";
+import "./CollectionPDFView.scss";
import React = require("react");
-import { FieldId } from "../../../fields/Field";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
+import { emptyFunction } from "../../../Utils";
+import { NumCast } from "../../../new_fields/Types";
+import { Id } from "../../../new_fields/RefField";
@observer
-export class CollectionPDFView extends React.Component<CollectionViewProps> {
+export class CollectionPDFView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldKey: string = "DataKey") {
- return `<${CollectionPDFView.name} Document={Document}
- ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
- isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
+ public static LayoutString(fieldKey: string = "data") {
+ return FieldView.LayoutString(CollectionPDFView, fieldKey);
}
+ @observable _inThumb = false;
- public SelectedDocs: FieldId[] = []
- @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : 0;
- @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : 0;
+ private set curPage(value: number) { this.props.Document.curPage = value; }
+ private get curPage() { return NumCast(this.props.Document.curPage, -1); }
+ private get numPages() { return NumCast(this.props.Document.numPages); }
+ @action onPageBack = () => this.curPage > 1 ? (this.props.Document.curPage = this.curPage - 1) : -1;
+ @action onPageForward = () => this.curPage < this.numPages ? (this.props.Document.curPage = this.curPage + 1) : -1;
- @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 0); }
- @computed private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); }
- @computed private get uIButtons() {
+ @action
+ onThumbDown = (e: React.PointerEvent) => {
+ document.addEventListener("pointermove", this.onThumbMove, false);
+ document.addEventListener("pointerup", this.onThumbUp, false);
+ e.stopPropagation();
+ this._inThumb = true;
+ }
+ @action
+ onThumbMove = (e: PointerEvent) => {
+ let pso = (e.clientY - (e as any).target.parentElement.getBoundingClientRect().top) / (e as any).target.parentElement.getBoundingClientRect().height;
+ this.curPage = Math.trunc(Math.min(this.numPages, pso * this.numPages + 1));
+ e.stopPropagation();
+ }
+ @action
+ onThumbUp = (e: PointerEvent) => {
+ this._inThumb = false;
+ document.removeEventListener("pointermove", this.onThumbMove);
+ document.removeEventListener("pointerup", this.onThumbUp);
+ }
+ nativeWidth = () => NumCast(this.props.Document.nativeWidth);
+ nativeHeight = () => NumCast(this.props.Document.nativeHeight);
+ private get uIButtons() {
+ let ratio = (this.curPage - 1) / this.numPages * 100;
return (
- <div className="pdfBox-buttonTray" key="tray">
- <button className="pdfButton" onClick={this.onPageBack}>{"<"}</button>
- <button className="pdfButton" onClick={this.onPageForward}>{">"}</button>
- </div>);
+ <div className="collectionPdfView-buttonTray" key="tray" style={{ height: "100%" }}>
+ <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
+ <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
+ <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} >
+ <div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} />
+ </div>
+ </div>
+ );
}
- // "inherited" CollectionView API starts here...
-
- public active: () => boolean = () => CollectionView.Active(this);
-
- addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
- removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
-
- specificContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "PDFOptions", event: () => { }, icon: "file-pdf" });
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "PDFOptions", event: emptyFunction, icon: "file-pdf" });
}
}
- get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; }
- get subView(): any { return CollectionView.SubView(this); }
+ private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ let props = { ...this.props, ...renderProps };
+ return (
+ <>
+ <CollectionFreeFormView {...props} CollectionView={this} />
+ {this.props.isSelected() ? this.uIButtons : (null)}
+ </>
+ );
+ }
render() {
- return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}>
- {this.subView}
- {this.props.isSelected() ? this.uIButtons : (null)}
- </div>)
+ return (
+ <CollectionBaseView {...this.props} className={`collectionPdfView-cont${this._inThumb ? "-dragging" : ""}`} onContextMenu={this.onContextMenu}>
+ {this.subView}
+ </CollectionBaseView>
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index d40e6d314..5e9d2ac67 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -1,54 +1,110 @@
+@import "../globalCssVariables";
+
.collectionSchemaView-container {
+ border-width: $COLLECTION_BORDER_WIDTH;
+ border-color : $intermediate-color;
border-style: solid;
+ border-radius: $border-radius;
box-sizing: border-box;
position: absolute;
width: 100%;
height: 100%;
+
+ .collectionSchemaView-cellContents {
+ height: $MAX_ROW_HEIGHT;
+ img {
+ width:auto;
+ max-height: $MAX_ROW_HEIGHT;
+ }
+ }
+
.collectionSchemaView-previewRegion {
- position: relative;
- background: black;
- float: left;
+ position: relative;
+ background: $light-color;
+ float: left;
height: 100%;
+ .collectionSchemaView-previewDoc {
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ }
+ .collectionSchemaView-input {
+ position: absolute;
+ max-width: 150px;
+ width: 100%;
+ bottom: 0px;
+ }
+ .documentView-node:first-child {
+ position: relative;
+ background: $light-color;
+ }
}
.collectionSchemaView-previewHandle {
position: absolute;
- height: 37px;
- width: 20px;
+ height: 15px;
+ width: 15px;
z-index: 20;
right: 0;
- top: 0;
+ top: 20px;
background: Black ;
}
.collectionSchemaView-dividerDragger{
position: relative;
background: black;
float: left;
+ height: 37px;
+ width: 20px;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ background: $main-accent;
+ }
+ .collectionSchemaView-columnsHandle {
+ position: absolute;
+ height: 37px;
+ width: 20px;
+ z-index: 20;
+ left: 0;
+ bottom: 0;
+ background: $main-accent;
+ }
+ .collectionSchemaView-colDividerDragger {
+ position: relative;
+ box-sizing: border-box;
+ border-top: 1px solid $intermediate-color;
+ border-bottom: 1px solid $intermediate-color;
+ float: top;
+ width: 100%;
+ }
+ .collectionSchemaView-dividerDragger {
+ position: relative;
+ box-sizing: border-box;
+ border-left: 1px solid $intermediate-color;
+ border-right: 1px solid $intermediate-color;
+ float: left;
height: 100%;
}
.collectionSchemaView-tableContainer {
position: relative;
float: left;
- height: 100%;
+ height: 100%;
}
-
.ReactTable {
- position: absolute;
- // display: inline-block;
- // overflow: auto;
+ // position: absolute; // display: inline-block;
+ // overflow: auto;
width: 100%;
height: 100%;
- background: white;
+ background: $light-color;
box-sizing: border-box;
+ border: none !important;
.rt-table {
overflow-y: auto;
overflow-x: auto;
height: 100%;
-
display: -webkit-inline-box;
- direction: ltr;
- // direction:rtl;
+ direction: ltr; // direction:rtl;
// display:block;
}
.rt-tbody {
@@ -57,45 +113,113 @@
}
.rt-tr-group {
direction: ltr;
- max-height: 44px;
+ max-height: $MAX_ROW_HEIGHT;
}
.rt-td {
- border-width: 1;
- border-right-color: #aaa;
+ border-width: 1px;
+ border-right-color: $intermediate-color;
.imageBox-cont {
- position:relative;
- max-height:100%;
+ position: relative;
+ max-height: 100%;
}
.imageBox-cont img {
object-fit: contain;
max-width: 100%;
- height: 100%
+ height: 100%;
+ }
+ .videoBox-cont {
+ object-fit: contain;
+ width: auto;
+ height: 100%;
}
- }
- .rt-tr-group {
- border-width: 1;
- border-bottom-color: #aaa
}
}
.ReactTable .rt-thead.-header {
- background:grey;
- }
- .ReactTable .rt-th, .ReactTable .rt-td {
- max-height: 44;
+ background: $intermediate-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 12px;
+ height: 30px;
+ padding-top: 4px;
+ }
+ .ReactTable .rt-th,
+ .ReactTable .rt-td {
+ max-height: $MAX_ROW_HEIGHT;
padding: 3px 7px;
+ font-size: 13px;
+ text-align: center;
}
.ReactTable .rt-tbody .rt-tr-group:last-child {
- border-bottom: grey;
+ border-bottom: $intermediate-color;
border-bottom-style: solid;
border-bottom-width: 1;
}
+ .documentView-node-topmost {
+ text-align:left;
+ transform-origin: center top;
+ display: inline-block;
+ }
.documentView-node:first-child {
- background: grey;
- .imageBox-cont img {
- object-fit: contain;
- }
+ background: $light-color;
}
}
+//options menu styling
+#schemaOptionsMenuBtn {
+ position: absolute;
+ height: 20px;
+ width: 20px;
+ border-radius: 50%;
+ z-index: 21;
+ right: 4px;
+ top: 4px;
+ pointer-events: auto;
+ background-color:black;
+ display:inline-block;
+ padding: 0px;
+ font-size: 100%;
+}
+
+ul {
+ list-style-type: disc;
+}
+
+#schema-options-header {
+ text-align: center;
+ padding: 0px;
+ margin: 0px;
+}
+.schema-options-subHeader {
+ color: $intermediate-color;
+ margin-bottom: 5px;
+}
+#schemaOptionsMenuBtn:hover {
+ transform: scale(1.15);
+}
+
+#preview-schema-checkbox-div {
+ margin-left: 20px;
+ font-size: 12px;
+}
+
+ #options-flyout-div {
+ text-align: left;
+ padding:0px;
+ z-index: 100;
+ font-family: $sans-serif;
+ padding-left: 5px;
+ }
+
+ #schema-col-checklist {
+ overflow: scroll;
+ text-align: left;
+ //background-color: $light-color-secondary;
+ line-height: 25px;
+ max-height: 175px;
+ font-family: $sans-serif;
+ font-size: 12px;
+ }
+
.Resizer {
box-sizing: border-box;
@@ -204,4 +328,12 @@
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
+}
+
+.-even {
+ background: $light-color !important;
+}
+
+.-odd {
+ background: $light-color-secondary !important;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 04f017378..e2b90e26d 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,84 +1,131 @@
-import React = require("react")
-import { action, observable } from "mobx";
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, untracked, runInAction } from "mobx";
import { observer } from "mobx-react";
-import Measure from "react-measure";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
+import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
import "react-table/react-table.css";
-import { Document } from "../../../fields/Document";
-import { Field } from "../../../fields/Field";
-import { KeyStore } from "../../../fields/KeyStore";
-import { CompileScript, ToField } from "../../util/Scripting";
+import { emptyFunction, returnFalse, returnZero } from "../../../Utils";
+import { SetupDrag } from "../../util/DragManager";
+import { CompileScript } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
-import { ContextMenu } from "../ContextMenu";
+import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";
+import { anchorPoints, Flyout } from "../DocumentDecorations";
+import '../DocumentDecorations.scss';
import { EditableView } from "../EditableView";
import { DocumentView } from "../nodes/DocumentView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
-import { CollectionViewBase } from "./CollectionViewBase";
-import { setupDrag } from "../../util/DragManager";
+import { CollectionSubView } from "./CollectionSubView";
+import { Opt, Field, Doc, DocListCastAsync, DocListCast } from "../../../new_fields/Doc";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
+import { List } from "../../../new_fields/List";
+import { Id } from "../../../new_fields/RefField";
+import { Gateway } from "../../northstar/manager/Gateway";
+import { Docs } from "../../documents/Documents";
+import { ContextMenu } from "../ContextMenu";
+
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@observer
-export class CollectionSchemaView extends CollectionViewBase {
- private _mainCont = React.createRef<HTMLDivElement>();
- private DIVIDER_WIDTH = 5;
-
- @observable _contentScaling = 1; // used to transfer the dimensions of the content pane in the DOM to the ContentScaling prop of the DocumentView
- @observable _dividerX = 0;
- @observable _panelWidth = 0;
- @observable _panelHeight = 0;
+class KeyToggle extends React.Component<{ keyName: string, checked: boolean, toggle: (key: string) => void }> {
+ constructor(props: any) {
+ super(props);
+ }
+
+ render() {
+ return (
+ <div key={this.props.keyName}>
+ <input type="checkbox" checked={this.props.checked} onChange={() => this.props.toggle(this.props.keyName)} />
+ {this.props.keyName}
+ </div>
+ );
+ }
+}
+
+@observer
+export class CollectionSchemaView extends CollectionSubView(doc => doc) {
+ private _mainCont?: HTMLDivElement;
+ private _startSplitPercent = 0;
+ private DIVIDER_WIDTH = 4;
+
+ @observable _columns: Array<string> = ["title", "data", "author"];
@observable _selectedIndex = 0;
- @observable _splitPercentage: number = 50;
+ @observable _columnsPercentage = 0;
+ @observable _keys: string[] = [];
+ @observable _newKeyName: string = "";
+
+ @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage); }
+ @computed get columns() { return Cast(this.props.Document.schemaColumns, listSpec("string"), []); }
+ @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
- doc: rowProps.value[0],
+ Document: rowProps.value[0],
fieldKey: rowProps.value[1],
- isSelected: () => false,
- select: () => { },
+ ContainingCollectionView: this.props.CollectionView,
+ isSelected: returnFalse,
+ select: emptyFunction,
isTopMost: false,
- bindings: {},
selectOnLoad: false,
- }
- let contents = (
- <FieldView {...props} />
- )
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ active: returnFalse,
+ whenActiveChanged: emptyFunction,
+ PanelHeight: returnZero,
+ PanelWidth: returnZero,
+ };
+ let fieldContentView = <FieldView {...props} />;
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, () => props.doc);
+ let onItemDown = (e: React.PointerEvent) =>
+ (this.props.CollectionView.props.isSelected() ?
+ SetupDrag(reference, () => props.Document, this.props.moveDocument)(e) : undefined);
+ let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
+ const res = run({ this: doc });
+ if (!res.success) return false;
+ doc[props.fieldKey] = res.result;
+ return true;
+ };
return (
- <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}>
- <EditableView contents={contents}
- height={36} GetValue={() => {
- let field = props.doc.Get(props.fieldKey);
- if (field && field instanceof Field) {
- return field.ToScriptString();
+ <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document[Id]} ref={reference}>
+ <EditableView
+ display={"inline"}
+ contents={fieldContentView}
+ height={Number(MAX_ROW_HEIGHT)}
+ GetValue={() => {
+ let field = props.Document[props.fieldKey];
+ if (field) {
+ //TODO Types
+ // return field.ToScriptString();
+ return String(field);
}
- return field || "";
+ return "";
}}
SetValue={(value: string) => {
- let script = CompileScript(value, undefined, true);
+ let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
if (!script.compiled) {
return false;
}
- let field = script();
- if (field instanceof Field) {
- props.doc.Set(props.fieldKey, field);
- return true;
- } else {
- let dataField = ToField(field);
- if (dataField) {
- props.doc.Set(props.fieldKey, dataField);
- return true;
- }
+ return applyToDoc(props.Document, script.run);
+ }}
+ OnFillDown={async (value: string) => {
+ let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
+ if (!script.compiled) {
+ return;
}
- return false;
+ const run = script.run;
+ //TODO This should be able to be refactored to compile the script once
+ const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
+ val && val.forEach(doc => applyToDoc(doc, run));
}}>
</EditableView>
- </div>
- )
+ </div >
+ );
}
private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
@@ -88,159 +135,240 @@ export class CollectionSchemaView extends CollectionViewBase {
}
return {
onClick: action((e: React.MouseEvent, handleOriginal: Function) => {
+ that.props.select(e.ctrlKey);
that._selectedIndex = rowInfo.index;
- this._splitPercentage += 0.05; // bcz - ugh - needed to force Measure to do its thing and call onResize
if (handleOriginal) {
- handleOriginal()
+ handleOriginal();
}
}),
style: {
- background: rowInfo.index == this._selectedIndex ? "lightGray" : "white",
- //color: rowInfo.index == this._selectedIndex ? "white" : "black"
+ background: rowInfo.index === this._selectedIndex ? "lightGray" : "white",
+ //color: rowInfo.index === this._selectedIndex ? "white" : "black"
}
};
}
- _startSplitPercent = 0;
+ private createTarget = (ele: HTMLDivElement) => {
+ this._mainCont = ele;
+ super.CreateDropTarget(ele);
+ }
+
+ @action
+ toggleKey = (key: string) => {
+ let list = Cast(this.props.Document.schemaColumns, listSpec("string"));
+ if (list === undefined) {
+ this.props.Document.schemaColumns = list = new List<string>([key]);
+ } else {
+ const index = list.indexOf(key);
+ if (index === -1) {
+ list.push(key);
+ } else {
+ list.splice(index, 1);
+ }
+ }
+ }
+
+ //toggles preview side-panel of schema
+ @action
+ toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => {
+ this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;
+ }
+
@action
onDividerMove = (e: PointerEvent): void => {
- let nativeWidth = this._mainCont.current!.getBoundingClientRect();
- this._splitPercentage = Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100);
+ let nativeWidth = this._mainCont!.getBoundingClientRect();
+ this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
}
@action
onDividerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
- if (this._startSplitPercent == this._splitPercentage) {
- this._splitPercentage = this._splitPercentage == 1 ? 66 : 100;
+ if (this._startSplitPercent === this.splitPercentage) {
+ this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;
}
}
onDividerDown = (e: React.PointerEvent) => {
- this._startSplitPercent = this._splitPercentage;
+ this._startSplitPercent = this.splitPercentage;
e.stopPropagation();
e.preventDefault();
document.addEventListener("pointermove", this.onDividerMove);
document.addEventListener('pointerup', this.onDividerUp);
}
- @action
- onExpanderMove = (e: PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (this.props.isSelected()) e.stopPropagation();
+ else e.preventDefault();
+ }
}
- @action
- onExpanderUp = (e: PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
- document.removeEventListener("pointermove", this.onExpanderMove);
- document.removeEventListener('pointerup', this.onExpanderUp);
- if (this._startSplitPercent == this._splitPercentage) {
- this._splitPercentage = this._splitPercentage == 100 ? 66 : 100;
+
+ onWheel = (e: React.WheelEvent): void => {
+ if (this.props.active()) {
+ e.stopPropagation();
}
}
- onExpanderDown = (e: React.PointerEvent) => {
- this._startSplitPercent = this._splitPercentage;
- e.stopPropagation();
- e.preventDefault();
- document.addEventListener("pointermove", this.onExpanderMove);
- document.addEventListener('pointerup', this.onExpanderUp);
- }
-
- onPointerDown = (e: React.PointerEvent) => {
- // if (e.button === 2 && this.active) {
- // e.stopPropagation();
- // e.preventDefault();
- // } else
- {
- if (e.buttons === 1) {
- if (this.props.isSelected()) {
- e.stopPropagation();
- }
+
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB });
+ }
+ }
+
+ @action
+ makeDB = async () => {
+ let csv: string = this.columns.reduce((val, col) => val + col + ",", "");
+ csv = csv.substr(0, csv.length - 1) + "\n";
+ let self = this;
+ DocListCast(this.props.Document.data).map(doc => {
+ csv += self.columns.reduce((val, col) => val + (doc[col] ? doc[col]!.toString() : "") + ",", "");
+ csv = csv.substr(0, csv.length - 1) + "\n";
+ });
+ csv.substring(0, csv.length - 1);
+ let dbName = StrCast(this.props.Document.title);
+ let res = await Gateway.Instance.PostSchema(csv, dbName);
+ if (self.props.CollectionView.props.addDocument) {
+ let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName });
+ if (schemaDoc) {
+ self.props.CollectionView.props.addDocument(schemaDoc, false);
}
}
}
@action
- setScaling = (r: any) => {
- const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
- const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
- this._panelWidth = r.entry.width;
- this._panelHeight = r.entry.height ? r.entry.height : this._panelHeight;
- this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width);
+ addColumn = () => {
+ this.columns.push(this._newKeyName);
+ this._newKeyName = "";
}
- getContentScaling = (): number => this._contentScaling;
- getPanelWidth = (): number => this._panelWidth;
- getPanelHeight = (): number => this._panelHeight;
- getTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling);
+ @action
+ newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._newKeyName = e.currentTarget.value;
}
- focusDocument = (doc: Document) => { }
+ @observable previewScript: string = "";
+ @action
+ onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.previewScript = e.currentTarget.value;
+ }
- render() {
- const columns = this.props.Document.GetList(KeyStore.ColumnsKey, [KeyStore.Title, KeyStore.Data, KeyStore.Author])
- const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
- const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
- let content = this._selectedIndex == -1 || !selected ? (null) : (
- <Measure onResize={this.setScaling}>
- {({ measureRef }) =>
- <div className="collectionSchemaView-content" ref={measureRef}>
- <DocumentView Document={selected}
- AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument}
- isTopMost={false}
- SelectOnLoad={false}
- ScreenToLocalTransform={this.getTransform}
- ContentScaling={this.getContentScaling}
- PanelWidth={this.getPanelWidth}
- PanelHeight={this.getPanelHeight}
- ContainingCollectionView={this.props.CollectionView}
- focus={this.focusDocument}
- />
+ @computed
+ get previewDocument(): Doc | undefined {
+ const children = DocListCast(this.props.Document[this.props.fieldKey]);
+ const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : undefined;
+ return selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
+ }
+ get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); }
+ get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; }
+ get previewRegionWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * this.splitPercentage / 100; }
+
+ private previewDocNativeWidth = () => Cast(this.previewDocument!.nativeWidth, "number", this.previewRegionWidth);
+ private previewDocNativeHeight = () => Cast(this.previewDocument!.nativeHeight, "number", this.previewRegionHeight);
+ private previewContentScaling = () => {
+ let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth);
+ if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight) {
+ return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight);
+ }
+ return wscale;
+ }
+ private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling();
+ private previewPanelHeight = () => this.previewDocNativeHeight() * this.previewContentScaling();
+ get previewPanelCenteringOffset() { return (this.previewRegionWidth - this.previewDocNativeWidth() * this.previewContentScaling()) / 2; }
+ getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
+ - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth - this.previewPanelCenteringOffset,
+ - this.borderWidth).scale(1 / this.previewContentScaling())
+
+ @computed
+ get previewPanel() {
+ // let doc = CompileScript(this.previewScript, { this: selected }, true)();
+ const previewDoc = this.previewDocument;
+ return (<div className="collectionSchemaView-previewRegion" style={{ width: `${Math.max(0, this.previewRegionWidth - 1)}px` }}>
+ {!previewDoc || !this.previewRegionWidth ? (null) : (
+ <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
+ <DocumentView Document={previewDoc} isTopMost={false} selectOnLoad={false}
+ toggleMinimized={emptyFunction}
+ addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ ContentScaling={this.previewContentScaling}
+ PanelWidth={this.previewPanelWidth} PanelHeight={this.previewPanelHeight}
+ ContainingCollectionView={this.props.CollectionView}
+ focus={emptyFunction}
+ parentActive={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ bringToFront={emptyFunction}
+ />
+ </div>)}
+ <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange}
+ style={{ left: `calc(50% - ${Math.min(75, (previewDoc ? this.previewPanelWidth() / 2 : 75))}px)` }} />
+ </div>);
+ }
+
+ get documentKeysCheckList() {
+ const docs = DocListCast(this.props.Document[this.props.fieldKey]);
+ let keys: { [key: string]: boolean } = {};
+ // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
+ // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
+ // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
+ // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
+ // is displayed (unlikely) it won't show up until something else changes.
+ //TODO Types
+ untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false))));
+
+ this.columns.forEach(key => keys[key] = true);
+ return Array.from(Object.keys(keys)).map(item =>
+ (<KeyToggle checked={keys[item]} key={item} keyName={item} toggle={this.toggleKey} />));
+ }
+
+ get tableOptionsPanel() {
+ return !this.props.active() ? (null) :
+ (<Flyout
+ anchorPoint={anchorPoints.RIGHT_TOP}
+ content={<div>
+ <div id="schema-options-header"><h5><b>Options</b></h5></div>
+ <div id="options-flyout-div">
+ <h6 className="schema-options-subHeader">Preview Window</h6>
+ <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div>
+ <h6 className="schema-options-subHeader" >Displayed Columns</h6>
+ <ul id="schema-col-checklist" >
+ {this.documentKeysCheckList}
+ </ul>
+ <input value={this._newKeyName} onChange={this.newKeyChange} />
+ <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>
</div>
- }
- </Measure>
- )
- let previewHandle = !this.props.active() ? (null) : (
- <div className="collectionSchemaView-previewHandle" onPointerDown={this.onExpanderDown} />);
- let dividerDragger = this._splitPercentage == 100 ? (null) :
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
+ </div>
+ }>
+ <button id="schemaOptionsMenuBtn" ><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
+ </Flyout>);
+ }
+
+ @computed
+ get dividerDragger() {
+ return this.splitPercentage === 0 ? (null) :
+ <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
+ }
+
+ render() {
+ library.add(faCog);
+ library.add(faPlus);
+ const children = this.children;
return (
- <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
- <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
- <Measure onResize={action((r: any) => {
- this._dividerX = r.entry.width;
- this._panelHeight = r.entry.height;
- })}>
- {({ measureRef }) =>
- <div ref={measureRef} className="collectionSchemaView-tableContainer" style={{ width: `${this._splitPercentage}%` }}>
- <ReactTable
- data={children}
- pageSize={children.length}
- page={0}
- showPagination={false}
- columns={columns.map(col => ({
- Header: col.Name,
- accessor: (doc: Document) => [doc, col],
- id: col.Id
- }))}
- column={{
- ...ReactTableDefaults.column,
- Cell: this.renderCell,
-
- }}
- getTrProps={this.getTrProps}
- />
- </div>
- }
- </Measure>
- {dividerDragger}
- <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${100 - this._splitPercentage}% - ${this.DIVIDER_WIDTH}px)` }}>
- {content}
- </div>
- {previewHandle}
+ <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
+ onDrop={(e: React.DragEvent) => this.onDrop(e, {})} onContextMenu={this.onContextMenu} ref={this.createTarget}>
+ <div className="collectionSchemaView-tableContainer" style={{ width: `${this.tableWidth}px` }}>
+ <ReactTable data={children} page={0} pageSize={children.length} showPagination={false}
+ columns={this.columns.map(col => ({
+ Header: col,
+ accessor: (doc: Doc) => [doc, col],
+ id: col
+ }))}
+ column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
+ getTrProps={this.getTrProps}
+ />
</div>
- </div >
- )
+ {this.dividerDragger}
+ {this.previewPanel}
+ {this.tableOptionsPanel}
+ </div>
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
new file mode 100644
index 000000000..ffd3e0659
--- /dev/null
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -0,0 +1,231 @@
+import { action, runInAction } from "mobx";
+import React = require("react");
+import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { DragManager } from "../../util/DragManager";
+import { Docs, DocumentOptions } from "../../documents/Documents";
+import { RouteStore } from "../../../server/RouteStore";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { FieldViewProps } from "../nodes/FieldView";
+import * as rp from 'request-promise';
+import { CollectionView } from "./CollectionView";
+import { CollectionPDFView } from "./CollectionPDFView";
+import { CollectionVideoView } from "./CollectionVideoView";
+import { Doc, Opt, FieldResult, DocListCast } from "../../../new_fields/Doc";
+import { DocComponent } from "../DocComponent";
+import { listSpec } from "../../../new_fields/Schema";
+import { Cast, PromiseValue, FieldValue, ListSpec } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
+import { DocServer } from "../../DocServer";
+import { ObjectField } from "../../../new_fields/ObjectField";
+import CursorField, { CursorPosition, CursorMetadata } from "../../../new_fields/CursorField";
+import { url } from "inspector";
+
+export interface CollectionViewProps extends FieldViewProps {
+ addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+ removeDocument: (document: Doc) => boolean;
+ moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+}
+
+export interface SubCollectionViewProps extends CollectionViewProps {
+ CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+}
+
+export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
+ class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
+ private dropDisposer?: DragManager.DragDropDisposer;
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+ protected CreateDropTarget(ele: HTMLDivElement) {
+ this.createDropTarget(ele);
+ }
+
+ get children() {
+ //TODO tfs: This might not be what we want?
+ //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
+ return DocListCast(this.props.Document[this.props.fieldKey]);
+ }
+
+ @action
+ protected async setCursorPosition(position: [number, number]) {
+ let ind;
+ let doc = this.props.Document;
+ let id = CurrentUserUtils.id;
+ let email = CurrentUserUtils.email;
+ let pos = { x: position[0], y: position[1] };
+ if (id && email) {
+ const proto = await doc.proto;
+ if (!proto) {
+ return;
+ }
+ let cursors = Cast(proto.cursors, listSpec(CursorField));
+ if (!cursors) {
+ proto.cursors = cursors = new List<CursorField>();
+ }
+ if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
+ cursors[ind].setPosition(pos);
+ } else {
+ let entry = new CursorField({ metadata: { id: id, identifier: email }, position: pos });
+ cursors.push(entry);
+ }
+ }
+ }
+
+ @undoBatch
+ @action
+ protected drop(e: Event, de: DragManager.DropEvent): boolean {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.data.dropAction || de.data.userDropAction) {
+ ["width", "height", "curPage"].map(key =>
+ de.data.draggedDocuments.map((draggedDocument: Doc, i: number) =>
+ PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f))));
+ }
+ let added = false;
+ if (de.data.dropAction || de.data.userDropAction) {
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+ let moved = this.props.addDocument(d);
+ return moved || added;
+ }, false);
+ } else if (de.data.moveDocument) {
+ const move = de.data.moveDocument;
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+ let moved = move(d, this.props.Document, this.props.addDocument);
+ return moved || added;
+ }, false);
+ } else {
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => {
+ let moved = this.props.addDocument(d);
+ return moved || added;
+ }, false);
+ }
+ e.stopPropagation();
+ return added;
+ }
+ return false;
+ }
+
+ protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
+ let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
+ if (type.indexOf("image") !== -1) {
+ ctor = Docs.ImageDocument;
+ }
+ if (type.indexOf("video") !== -1) {
+ ctor = Docs.VideoDocument;
+ }
+ if (type.indexOf("audio") !== -1) {
+ ctor = Docs.AudioDocument;
+ }
+ if (type.indexOf("pdf") !== -1) {
+ ctor = Docs.PdfDocument;
+ options.nativeWidth = 1200;
+ }
+ if (type.indexOf("excel") !== -1) {
+ ctor = Docs.DBDocument;
+ options.dropAction = "copy";
+ }
+ if (type.indexOf("html") !== -1) {
+ if (path.includes('localhost')) {
+ let s = path.split('/');
+ let id = s[s.length - 1];
+ DocServer.GetRefField(id).then(field => {
+ if (field instanceof Doc) {
+ let alias = Doc.MakeAlias(field);
+ alias.x = options.x || 0;
+ alias.y = options.y || 0;
+ alias.width = options.width || 300;
+ alias.height = options.height || options.width || 300;
+ this.props.addDocument(alias, false);
+ }
+ });
+ return undefined;
+ }
+ ctor = Docs.WebDocument;
+ options = { height: options.width, ...options, title: path, nativeWidth: undefined };
+ }
+ return ctor ? ctor(path, options) : undefined;
+ }
+
+ @undoBatch
+ @action
+ protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
+ let html = e.dataTransfer.getData("text/html");
+ let text = e.dataTransfer.getData("text/plain");
+
+ if (text && text.startsWith("<div")) {
+ return;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
+ let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
+ this.props.addDocument(htmlDoc, false);
+ return;
+ }
+ if (text && text.indexOf("www.youtube.com/watch") !== -1) {
+ const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+ this.props.addDocument(Docs.WebDocument(url, { ...options, width: 300, height: 300 }));
+ return;
+ }
+
+ let batch = UndoManager.StartBatch("collection view drop");
+ let promises: Promise<void>[] = [];
+ // tslint:disable-next-line:prefer-for-of
+ for (let i = 0; i < e.dataTransfer.items.length; i++) {
+ const upload = window.location.origin + RouteStore.upload;
+ let item = e.dataTransfer.items[i];
+ if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
+ let str: string;
+ let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
+ .then(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s)))))
+ .then(result => {
+ let type = result["content-type"];
+ if (type) {
+ this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+ .then(doc => doc && this.props.addDocument(doc, false));
+ }
+ });
+ promises.push(prom);
+ }
+ let type = item.type;
+ if (item.kind === "file") {
+ let file = item.getAsFile();
+ let formData = new FormData();
+
+ if (file) {
+ formData.append('file', file);
+ }
+ let dropFileName = file ? file.name : "-empty-";
+
+ let prom = fetch(upload, {
+ method: 'POST',
+ body: formData
+ }).then(async (res: Response) => {
+ (await res.json()).map(action((file: any) => {
+ let path = window.location.origin + file;
+ let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
+
+ docPromise.then(doc => doc && this.props.addDocument(doc));
+ }));
+ });
+ promises.push(prom);
+ }
+ }
+
+ if (promises.length) {
+ Promise.all(promises).finally(() => batch.end());
+ } else {
+ batch.end();
+ }
+ }
+ }
+ return CollectionSubView;
+}
+
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index f8d580a7b..411d67ff7 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,37 +1,88 @@
-#body {
+@import "../globalCssVariables";
+
+.collectionTreeView-dropTarget {
+ border-width: $COLLECTION_BORDER_WIDTH;
+ border-color: transparent;
+ border-style: solid;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ height: 100%;
padding: 20px;
- background: #bbbbbb;
-}
+ padding-left: 10px;
+ padding-right: 0px;
+ background: $light-color-secondary;
+ font-size: 13px;
+ overflow: scroll;
-ul {
- list-style: none;
-}
+ ul {
+ list-style: none;
+ padding-left: 20px;
+ }
-li {
- margin: 5px 0;
-}
+ li {
+ margin: 5px 0;
+ }
-.no-indent {
- padding-left: 0;
-}
-.bullet {
- width: 1.5em;
- display: inline-block;
-}
+ .no-indent {
+ padding-left: 0;
+ }
-.collectionTreeView-dropTarget {
- border-style: solid;
- box-sizing: border-box;
- height: 100%;
-}
+ .bullet {
+ float:left;
+ position: relative;
+ width: 15px;
+ display: block;
+ color: $intermediate-color;
+ margin-top: 3px;
+ transform: scale(1.3,1.3);
+ }
+
+ .docContainer {
+ margin-left: 10px;
+ display: block;
+ // width:100%;//width: max-content;
+ }
+ .docContainer:hover {
+ .treeViewItem-openRight {
+ display:inline;
+ }
+ }
+
+
+ .editableView-container {
+ font-weight: bold;
+ }
-.docContainer {
- display: inline-table;
-}
+ .delete-button {
+ color: $intermediate-color;
+ // float: right;
+ margin-left: 15px;
+ // margin-top: 3px;
+ display: inline;
+ }
+ .treeViewItem-openRight {
+ margin-left: 5px;
+ display:none;
+ }
+ .docContainer:hover {
+ .delete-button {
+ display: inline;
+ // width: auto;
+ }
+ }
-.delete-button {
- color: #999999;
- float: right;
- margin-left: 1em;
+ .coll-title {
+ width:max-content;
+ display: block;
+ font-size: 24px;
+ }
+ .collection-child {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ }
+ .collectionTreeView-keyHeader {
+ font-style: italic;
+ font-size: 8pt;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8b06d9ac4..6acef434e 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,20 +1,32 @@
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretDown, faCaretRight, faTrashAlt, faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable, trace } from "mobx";
import { observer } from "mobx-react";
-import { CollectionViewBase } from "./CollectionViewBase";
-import { Document } from "../../../fields/Document";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import React = require("react")
-import { TextField } from "../../../fields/TextField";
-import { observable, action } from "mobx";
-import "./CollectionTreeView.scss";
+import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
import { EditableView } from "../EditableView";
-import { setupDrag } from "../../util/DragManager";
-import { FieldWaiting } from "../../../fields/Field";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionTreeView.scss";
+import React = require("react");
+import { Document, listSpec } from '../../../new_fields/Schema';
+import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
+import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { Id } from '../../../new_fields/RefField';
+import { ContextMenu } from '../ContextMenu';
+import { undoBatch } from '../../util/UndoManager';
+import { Main } from '../Main';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { CollectionDockingView } from './CollectionDockingView';
+import { DocumentManager } from '../../util/DocumentManager';
+import { List } from '../../../new_fields/List';
+import { Docs } from '../../documents/Documents';
+
export interface TreeViewProps {
- document: Document;
- deleteDoc: (doc: Document) => void;
+ document: Doc;
+ deleteDoc: (doc: Doc) => void;
+ moveDocument: DragManager.MoveFunction;
+ dropAction: "alias" | "copy" | undefined;
}
export enum BulletType {
@@ -23,151 +35,219 @@ export enum BulletType {
List
}
+library.add(faTrashAlt);
+library.add(faAngleRight);
+library.add(faCaretDown);
+library.add(faCaretRight);
+
@observer
/**
* Component that takes in a document prop and a boolean whether it's collapsed or not.
*/
class TreeView extends React.Component<TreeViewProps> {
- @observable
- collapsed: boolean = false;
+ @observable _collapsed: boolean = true;
+
+ @undoBatch delete = () => this.props.deleteDoc(this.props.document);
- delete = () => {
- this.props.deleteDoc(this.props.document);
+ @undoBatch openRight = async () => {
+ if (this.props.document.dockingConfig) {
+ Main.Instance.openWorkspace(this.props.document);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(this.props.document);
+ }
}
+ get children() {
+ return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed? .filter(doc => FieldValue(doc));
+ }
+
+ onPointerDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ }
@action
- remove = (document: Document) => {
- var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- if (children && children !== FieldWaiting) {
- children.Data.splice(children.Data.indexOf(document), 1);
+ remove = (document: Document, key: string) => {
+ let children = Cast(this.props.document[key], listSpec(Doc), []);
+ if (children) {
+ children.splice(children.indexOf(document), 1);
}
}
- renderBullet(type: BulletType) {
- let onClicked = action(() => this.collapsed = !this.collapsed);
+ @action
+ move: DragManager.MoveFunction = (document, target, addDoc) => {
+ if (this.props.document === target) {
+ return true;
+ }
+ //TODO This should check if it was removed
+ this.remove(document, "data");
+ return addDoc(document);
+ }
+ renderBullet(type: BulletType) {
+ let onClicked = action(() => this._collapsed = !this._collapsed);
+ let bullet: IconProp | undefined = undefined;
switch (type) {
- case BulletType.Collapsed:
- return <div className="bullet" onClick={onClicked}>&#9654;</div>
- case BulletType.Collapsible:
- return <div className="bullet" onClick={onClicked}>&#9660;</div>
- case BulletType.List:
- return <div className="bullet">&mdash;</div>
+ case BulletType.Collapsed: bullet = "caret-right"; break;
+ case BulletType.Collapsible: bullet = "caret-down"; break;
}
+ return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
}
+ @action
+ onMouseEnter = () => {
+ this._isOver = true;
+ }
+ @observable _isOver: boolean = false;
+ @action
+ onMouseLeave = () => {
+ this._isOver = false;
+ }
/**
* Renders the EditableView title element for placement into the tree.
*/
renderTitle() {
- let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
-
- // if the title hasn't loaded, immediately return the div
- if (!title || title === "<Waiting>") {
- return <div key={this.props.document.Id}></div>;
- }
-
- return <div className="docContainer"> <EditableView contents={title.Data}
- height={36} GetValue={() => {
- let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
- if (title && title !== "<Waiting>")
- return title.Data;
- return "";
- }} SetValue={(value: string) => {
- this.props.document.SetData(KeyStore.Title, value, TextField);
- return true;
- }} />
- <div className="delete-button" onClick={this.delete}>x</div>
- </div >
+ let reference = React.createRef<HTMLDivElement>();
+ let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction);
+ let editableView = (titleString: string) =>
+ (<EditableView
+ oneLine={!this._isOver ? true : false}
+ display={"block"}
+ contents={titleString}
+ height={36}
+ GetValue={() => StrCast(this.props.document.title)}
+ SetValue={(value: string) => {
+ let target = this.props.document.proto ? this.props.document.proto : this.props.document;
+ target.title = value;
+ return true;
+ }}
+ />);
+ let dataDocs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []);
+ let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
+ <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
+ <FontAwesomeIcon icon="angle-right" size="lg" />
+ <FontAwesomeIcon icon="angle-right" size="lg" />
+ </div>);
+ return (
+ <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
+ style={{ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ {editableView(StrCast(this.props.document.title))}
+ {openRight}
+ {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
+ </div >);
}
- render() {
- var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField);
-
- let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, () => this.props.document);
- let titleElement = this.renderTitle();
-
- // check if this document is a collection
- if (children && children !== FieldWaiting) {
- let subView;
-
- // if uncollapsed, then add the children elements
- if (!this.collapsed) {
- // render all children elements
- let childrenElement = (children.Data.map(value =>
- <TreeView document={value} deleteDoc={this.remove} />)
- )
- subView =
- <li key={this.props.document.Id} >
- {this.renderBullet(BulletType.Collapsible)}
- {titleElement}
- <ul key={this.props.document.Id}>
- {childrenElement}
- </ul>
- </li>
- } else {
- subView = <li key={this.props.document.Id}>
- {this.renderBullet(BulletType.Collapsed)}
- {titleElement}
- </li>
+ onWorkspaceContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) });
+ ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) });
+ ContextMenu.Instance.addItem({
+ description: "Open Fields", event: () => CollectionDockingView.Instance.AddRightSplit(Docs.KVPDocument(this.props.document,
+ { title: this.props.document.title + ".kvp", width: 300, height: 300 }))
+ });
+ if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
+ ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
}
-
- return <div className="treeViewItem-container" onPointerDown={onItemDown} ref={reference}>
- {subView}
- </div>
+ ContextMenu.Instance.addItem({
+ description: "Delete", event: undoBatch(() => {
+ this.props.deleteDoc(this.props.document);
+ })
+ });
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ e.stopPropagation();
}
+ }
+
+ onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; };
+ onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; };
- // otherwise this is a normal leaf node
- else {
- return <li key={this.props.document.Id}>
- {this.renderBullet(BulletType.List)}
- {titleElement}
- </li>;
+ render() {
+ let bulletType = BulletType.List;
+ let contentElement: (JSX.Element | null)[] = [];
+ let keys = Array.from(Object.keys(this.props.document));
+ if (this.props.document.proto instanceof Doc) {
+ keys.push(...Array.from(Object.keys(this.props.document.proto)));
+ while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
}
+ keys.map(key => {
+ let docList = DocListCast(this.props.document[key]);
+ let doc = Cast(this.props.document[key], Doc);
+ if (doc instanceof Doc || docList.length) {
+ if (!this._collapsed) {
+ bulletType = BulletType.Collapsible;
+ let spacing = (key === "data") ? 0 : -10;
+ contentElement.push(<ul key={key + "more"}>
+ {(key === "data") ? (null) :
+ <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>}
+ <div style={{ display: "block", marginTop: `${spacing}px` }}>
+ {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
+ </div>
+ </ul >);
+ } else {
+ bulletType = BulletType.Collapsed;
+ }
+ }
+ });
+ return <div className="treeViewItem-container"
+ onContextMenu={this.onWorkspaceContextMenu}>
+ <li className="collection-child">
+ {this.renderBullet(bulletType)}
+ {this.renderTitle()}
+ {contentElement}
+ </li>
+ </div>;
+ }
+ public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
+ return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child =>
+ <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
}
}
-
@observer
-export class CollectionTreeView extends CollectionViewBase {
-
+export class CollectionTreeView extends CollectionSubView(Document) {
@action
remove = (document: Document) => {
- var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- if (children && children !== FieldWaiting) {
- children.Data.splice(children.Data.indexOf(document), 1);
+ let children = Cast(this.props.Document.data, listSpec(Doc), []);
+ if (children) {
+ children.splice(children.indexOf(document), 1);
+ }
+ }
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => Main.Instance.createNewWorkspace()) });
+ }
+ if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
+ ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });
}
}
-
render() {
- let titleStr = "";
- let title = this.props.Document.GetT<TextField>(KeyStore.Title, TextField);
- if (title && title !== FieldWaiting) {
- titleStr = title.Data;
+ let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
+ if (!this.children) {
+ return (null);
}
-
- var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField);
- let childrenElement = !children || children === FieldWaiting ? (null) :
- (children.Data.map(value =>
- <TreeView document={value} key={value.Id} deleteDoc={this.remove} />)
- )
+ let childElements = TreeView.GetChildElements(this.children, false, this.remove, this.props.moveDocument, dropAction);
return (
- <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
- <h3>
- <EditableView contents={titleStr}
- height={72} GetValue={() => {
- return this.props.Document.Title;
- }} SetValue={(value: string) => {
- this.props.Document.SetData(KeyStore.Title, value, TextField);
+ <div id="body" className="collectionTreeView-dropTarget"
+ style={{ borderRadius: "inherit" }}
+ onContextMenu={this.onContextMenu}
+ onWheel={(e: React.WheelEvent) => e.stopPropagation()}
+ onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
+ <div className="coll-title">
+ <EditableView
+ contents={this.props.Document.title}
+ display={"inline"}
+ height={72}
+ GetValue={() => StrCast(this.props.Document.title)}
+ SetValue={(value: string) => {
+ let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ target.title = value;
return true;
}} />
- </h3>
+ </div>
<ul className="no-indent">
- {childrenElement}
+ {childElements}
</ul>
</div >
);
diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss
new file mode 100644
index 000000000..db8b84832
--- /dev/null
+++ b/src/client/views/collections/CollectionVideoView.scss
@@ -0,0 +1,42 @@
+
+.collectionVideoView-cont{
+ width: 100%;
+ height: 100%;
+ position: inherit;
+ top: 0;
+ left:0;
+
+}
+.collectionVideoView-time{
+ color : white;
+ top :25px;
+ left : 25px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+ transform-origin: left top;
+}
+.collectionVideoView-play {
+ width: 25px;
+ height: 20px;
+ bottom: 25px;
+ left : 25px;
+ position: absolute;
+ color : white;
+ background-color: rgba(50, 50, 50, 0.2);
+ border-radius: 4px;
+ text-align: center;
+ transform-origin: left bottom;
+}
+.collectionVideoView-full {
+ width: 25px;
+ height: 20px;
+ bottom: 25px;
+ right : 25px;
+ position: absolute;
+ color : white;
+ background-color: rgba(50, 50, 50, 0.2);
+ border-radius: 4px;
+ text-align: center;
+ transform-origin: right bottom;
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
new file mode 100644
index 000000000..9ab959f3c
--- /dev/null
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -0,0 +1,89 @@
+import { action, observable, trace } from "mobx";
+import { observer } from "mobx-react";
+import { ContextMenu } from "../ContextMenu";
+import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
+import React = require("react");
+import "./CollectionVideoView.scss";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import { emptyFunction } from "../../../Utils";
+import { Id } from "../../../new_fields/RefField";
+import { VideoBox } from "../nodes/VideoBox";
+import { NumCast } from "../../../new_fields/Types";
+
+
+@observer
+export class CollectionVideoView extends React.Component<FieldViewProps> {
+ private _videoBox?: VideoBox;
+
+ public static LayoutString(fieldKey: string = "data") {
+ return FieldView.LayoutString(CollectionVideoView, fieldKey);
+ }
+ private get uIButtons() {
+ let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
+ let curTime = NumCast(this.props.Document.curPage);
+ return ([
+ <div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ <span>{"" + Math.round(curTime)}</span>
+ <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
+ </div>,
+ <div className="collectionVideoView-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ {this._videoBox && this._videoBox.Playing ? "\"" : ">"}
+ </div>,
+ <div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ F
+ </div>
+ ]);
+ }
+
+ @action
+ onPlayDown = () => {
+ if (this._videoBox && this._videoBox.player) {
+ if (this._videoBox.Playing) {
+ this._videoBox.Pause();
+ } else {
+ this._videoBox.Play();
+ }
+ }
+ }
+
+ @action
+ onFullDown = (e: React.PointerEvent) => {
+ if (this._videoBox) {
+ this._videoBox.FullScreen();
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onResetDown = () => {
+ if (this._videoBox) {
+ this._videoBox.Pause();
+ this.props.Document.curPage = 0;
+ }
+ }
+
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ }
+ }
+
+ setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
+
+ private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ let props = { ...this.props, ...renderProps };
+ return (<>
+ <CollectionFreeFormView {...props} setVideoBox={this.setVideoBox} CollectionView={this} />
+ {this.props.isSelected() ? this.uIButtons : (null)}
+ </>);
+ }
+
+ render() {
+ trace();
+ return (
+ <CollectionBaseView {...this.props} className="collectionVideoView-cont" onContextMenu={this.onContextMenu}>
+ {this.subView}
+ </CollectionBaseView>);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d0e2162b1..3d9b4990a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,132 +1,56 @@
-import { action, computed, observable } from "mobx";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faProjectDiagram, faSquare, faTh, faTree } from '@fortawesome/free-solid-svg-icons';
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { ListField } from "../../../fields/ListField";
-import { SelectionManager } from "../../util/SelectionManager";
+import * as React from 'react';
+import { Id } from '../../../new_fields/RefField';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../ContextMenu";
-import React = require("react");
-import { KeyStore } from "../../../fields/KeyStore";
-import { NumberField } from "../../../fields/NumberField";
-import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView';
import { CollectionDockingView } from "./CollectionDockingView";
import { CollectionSchemaView } from "./CollectionSchemaView";
-import { CollectionViewProps } from "./CollectionViewBase";
import { CollectionTreeView } from "./CollectionTreeView";
-import { Field, FieldId } from "../../../fields/Field";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faTh } from '@fortawesome/free-solid-svg-icons';
-import { faTree } from '@fortawesome/free-solid-svg-icons';
-import { faSquare } from '@fortawesome/free-solid-svg-icons';
-import { faProjectDiagram } from '@fortawesome/free-solid-svg-icons';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+export const COLLECTION_BORDER_WIDTH = 2;
library.add(faTh);
library.add(faTree);
library.add(faSquare);
library.add(faProjectDiagram);
-export enum CollectionViewType {
- Invalid,
- Freeform,
- Schema,
- Docking,
- Tree
-}
-
-export const COLLECTION_BORDER_WIDTH = 2;
-
@observer
-export class CollectionView extends React.Component<CollectionViewProps> {
-
- @observable
- public SelectedDocs: FieldId[] = [];
-
- public static LayoutString(fieldKey: string = "DataKey") {
- return `<${CollectionView.name} Document={Document}
- ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
- isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
- }
-
- public active: () => boolean = () => CollectionView.Active(this);
- addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
- removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
- get subView() { return CollectionView.SubView(this); }
-
- public static Active(self: CollectionView): boolean {
- var isSelected = self.props.isSelected();
- var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == self);
- var topMost = self.props.isTopMost;
- return isSelected || childSelected || topMost;
- }
-
- @action
- public static AddDocument(props: CollectionViewProps, doc: Document) {
- doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, 0));
- if (props.Document.Get(props.fieldKey) instanceof Field) {
- //TODO This won't create the field if it doesn't already exist
- const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
- value.push(doc);
- } else {
- props.Document.SetData(props.fieldKey, [doc], ListField);
- }
- }
-
- @action
- public static RemoveDocument(props: CollectionViewProps, doc: Document): boolean {
- //TODO This won't create the field if it doesn't already exist
- const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
- let index = -1;
- for (let i = 0; i < value.length; i++) {
- if (value[i].Id == doc.Id) {
- index = i;
- break;
- }
- }
-
- if (index !== -1) {
- value.splice(index, 1)
-
- SelectionManager.DeselectAll()
- ContextMenu.Instance.clearItems()
- return true;
- }
- return false
- }
-
- get collectionViewType(): CollectionViewType {
- let Document = this.props.Document;
- let viewField = Document.GetT(KeyStore.ViewType, NumberField);
- if (viewField === "<Waiting>") {
- return CollectionViewType.Invalid;
- } else if (viewField) {
- return viewField.Data;
- } else {
- return CollectionViewType.Freeform;
+export class CollectionView extends React.Component<FieldViewProps> {
+ public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(CollectionView, fieldStr); }
+
+ private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
+ let props = { ...this.props, ...renderProps };
+ switch (type) {
+ case CollectionViewType.Schema: return (<CollectionSchemaView {...props} CollectionView={this} />);
+ case CollectionViewType.Docking: return (<CollectionDockingView {...props} CollectionView={this} />);
+ case CollectionViewType.Tree: return (<CollectionTreeView {...props} CollectionView={this} />);
+ case CollectionViewType.Freeform:
+ default:
+ return (<CollectionFreeFormView {...props} CollectionView={this} />);
}
+ return (null);
}
- specificContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform), icon: "project-diagram" })
- ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema), icon: "th" })
- ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree), icon: "tree" })
- ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking), icon: "square" })
- }
- }
+ get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
- public static SubView(self: CollectionView) {
- let subProps = { ...self.props, addDocument: self.addDocument, removeDocument: self.removeDocument, active: self.active, CollectionView: self }
- switch (self.collectionViewType) {
- case CollectionViewType.Freeform: return (<CollectionFreeFormView {...subProps} />)
- case CollectionViewType.Schema: return (<CollectionSchemaView {...subProps} />)
- case CollectionViewType.Docking: return (<CollectionDockingView {...subProps} />)
- case CollectionViewType.Tree: return (<CollectionTreeView {...subProps} />)
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "project-diagram" });
+ ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "project-diagram" });
+ ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
}
- return (null);
}
render() {
- return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}>
- {this.subView}
- </div>)
+ return (
+ <CollectionBaseView {...this.props} onContextMenu={this.onContextMenu}>
+ {this.SubView}
+ </CollectionBaseView>
+ );
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
deleted file mode 100644
index b126b40a9..000000000
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ /dev/null
@@ -1,152 +0,0 @@
-import { action, runInAction } from "mobx";
-import { Document } from "../../../fields/Document";
-import { ListField } from "../../../fields/ListField";
-import React = require("react");
-import { KeyStore } from "../../../fields/KeyStore";
-import { FieldWaiting } from "../../../fields/Field";
-import { undoBatch } from "../../util/UndoManager";
-import { DragManager } from "../../util/DragManager";
-import { DocumentView } from "../nodes/DocumentView";
-import { Documents, DocumentOptions } from "../../documents/Documents";
-import { Key } from "../../../fields/Key";
-import { Transform } from "../../util/Transform";
-import { CollectionView } from "./CollectionView";
-
-export interface CollectionViewProps {
- fieldKey: Key;
- Document: Document;
- ScreenToLocalTransform: () => Transform;
- isSelected: () => boolean;
- isTopMost: boolean;
- select: (ctrlPressed: boolean) => void;
- bindings: any;
- panelWidth: () => number;
- panelHeight: () => number;
- focus: (doc: Document) => void;
-}
-export interface SubCollectionViewProps extends CollectionViewProps {
- active: () => boolean;
- addDocument: (doc: Document) => void;
- removeDocument: (doc: Document) => boolean;
- CollectionView: CollectionView;
-}
-
-export class CollectionViewBase extends React.Component<SubCollectionViewProps> {
- private dropDisposer?: DragManager.DragDropDisposer;
- protected createDropTarget = (ele: HTMLDivElement) => {
- if (this.dropDisposer) {
- this.dropDisposer();
- }
- if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
- }
- }
-
- @undoBatch
- @action
- protected drop(e: Event, de: DragManager.DropEvent) {
- const docView: DocumentView = de.data["documentView"];
- const doc: Document = de.data["document"];
- if (docView && (!docView.props.ContainingCollectionView || docView.props.ContainingCollectionView !== this.props.CollectionView)) {
- if (docView.props.RemoveDocument) {
- docView.props.RemoveDocument(docView.props.Document);
- }
- this.props.addDocument(docView.props.Document);
- } else if (doc) {
- this.props.removeDocument(doc);
- this.props.addDocument(doc);
- }
- e.stopPropagation();
- }
-
- @action
- protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
- e.stopPropagation()
- e.preventDefault()
- let that = this;
-
- let html = e.dataTransfer.getData("text/html");
- let text = e.dataTransfer.getData("text/plain");
- if (html && html.indexOf("<img") != 0) {
- console.log("not good");
- let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
- htmlDoc.SetText(KeyStore.DocumentText, text);
- this.props.addDocument(htmlDoc);
- return;
- }
-
- console.log(e.dataTransfer.items.length);
-
- for (let i = 0; i < e.dataTransfer.items.length; i++) {
- const upload = window.location.origin + "/upload";
- let item = e.dataTransfer.items[i];
- if (item.kind === "string" && item.type.indexOf("uri") != -1) {
- e.dataTransfer.items[i].getAsString(function (s) {
- action(() => {
- var img = Documents.ImageDocument(s, { ...options, nativeWidth: 300, width: 300, })
-
- let docs = that.props.Document.GetT(KeyStore.Data, ListField);
- if (docs != FieldWaiting) {
- if (!docs) {
- docs = new ListField<Document>();
- that.props.Document.Set(KeyStore.Data, docs)
- }
- docs.Data.push(img);
- }
- })()
-
- })
- }
- let type = item.type
- console.log(type)
- if (item.kind == "file") {
- let fReader = new FileReader()
- let file = item.getAsFile();
- let formData = new FormData()
-
- if (file) {
- formData.append('file', file)
- }
-
- fetch(upload, {
- method: 'POST',
- body: formData
- })
- .then((res: Response) => {
- return res.json()
- }).then(json => {
-
- json.map((file: any) => {
- let path = window.location.origin + file
- runInAction(() => {
- var doc: any;
-
- if (type.indexOf("image") !== -1) {
- doc = Documents.ImageDocument(path, { ...options, nativeWidth: 300, width: 300, })
- }
- if (type.indexOf("video") !== -1) {
- doc = Documents.VideoDocument(path, { ...options, nativeWidth: 300, width: 300, })
- }
- if (type.indexOf("audio") !== -1) {
- doc = Documents.AudioDocument(path, { ...options, nativeWidth: 300, width: 300, })
- }
- let docs = that.props.Document.GetT(KeyStore.Data, ListField);
- if (docs != FieldWaiting) {
- if (!docs) {
- docs = new ListField<Document>();
- that.props.Document.Set(KeyStore.Data, docs)
- }
- if (doc) {
- docs.Data.push(doc);
- }
-
- }
- })
- })
- })
-
-
- }
- }
- }
-}
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
new file mode 100644
index 000000000..f3c605f3e
--- /dev/null
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -0,0 +1,8 @@
+.PDS-flyout {
+ position: absolute;
+ z-index: 9999;
+ background-color: #d3d3d3;
+ box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+ min-width: 150px;
+ color: black;
+} \ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
new file mode 100644
index 000000000..52f7914f3
--- /dev/null
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+import './ParentDocumentSelector.scss';
+import { Doc } from "../../../new_fields/Doc";
+import { observer } from "mobx-react";
+import { observable, action, runInAction } from "mobx";
+import { Id } from "../../../new_fields/RefField";
+import { SearchUtil } from "../../util/SearchUtil";
+import { CollectionDockingView } from "./CollectionDockingView";
+
+@observer
+export class SelectorContextMenu extends React.Component<{ Document: Doc }> {
+ @observable private _docs: Doc[] = [];
+
+ constructor(props: { Document: Doc }) {
+ super(props);
+
+ this.fetchDocuments();
+ }
+
+ async fetchDocuments() {
+ const docs = await SearchUtil.Search(`data_l:"${this.props.Document[Id]}"`, true);
+ runInAction(() => this._docs = docs);
+ }
+
+ render() {
+ return (
+ <>
+ {this._docs.map(doc => <p><a onClick={() => CollectionDockingView.Instance.AddRightSplit(doc)}>{doc.title}</a></p>)}
+ </>
+ );
+ }
+}
+
+@observer
+export class ParentDocSelector extends React.Component<{ Document: Doc }> {
+ @observable hover = false;
+
+ @action
+ onMouseLeave = () => {
+ this.hover = false;
+ }
+
+ @action
+ onMouseEnter = () => {
+ this.hover = true;
+ }
+
+ render() {
+ let flyout;
+ if (this.hover) {
+ flyout = (
+ <div className="PDS-flyout">
+ <SelectorContextMenu Document={this.props.Document} />
+ </div>
+ );
+ }
+ return (
+ <span style={{ position: "relative", display: "inline-block" }}
+ onMouseEnter={this.onMouseEnter}
+ onMouseLeave={this.onMouseLeave}>
+ <p>^</p>
+ {flyout}
+ </span>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
new file mode 100644
index 000000000..737ffba7d
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -0,0 +1,12 @@
+.collectionfreeformlinkview-linkLine {
+ stroke: black;
+ transform: translate(10000px,10000px);
+ opacity: 0.5;
+ pointer-events: all;
+}
+.collectionfreeformlinkview-linkCircle {
+ stroke: rgb(0,0,0);
+ opacity: 0.5;
+ transform: translate(10000px,10000px);
+ pointer-events: all;
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
new file mode 100644
index 000000000..63d2f7642
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -0,0 +1,58 @@
+import { observer } from "mobx-react";
+import { Utils } from "../../../../Utils";
+import "./CollectionFreeFormLinkView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+import { StrCast, NumCast, BoolCast } from "../../../../new_fields/Types";
+import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
+import { InkingControl } from "../../InkingControl";
+
+export interface CollectionFreeFormLinkViewProps {
+ A: Doc;
+ B: Doc;
+ LinkDocs: Doc[];
+ addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+ removeDocument: (document: Doc) => boolean;
+}
+
+@observer
+export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
+
+ onPointerDown = (e: React.PointerEvent) => {
+ if (e.button === 0 && !InkingControl.Instance.selectedTool) {
+ let a = this.props.A;
+ let b = this.props.B;
+ let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : a[WidthSym]() / 2);
+ let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2);
+ let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2);
+ let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2);
+ this.props.LinkDocs.map(l => {
+ let width = l[WidthSym]();
+ l.x = (x1 + x2) / 2 - width / 2;
+ l.y = (y1 + y2) / 2 + 10;
+ if (!this.props.removeDocument(l)) this.props.addDocument(l, false);
+ });
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+ render() {
+ let l = this.props.LinkDocs;
+ let a = this.props.A;
+ let b = this.props.B;
+ let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / 2);
+ let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / 2);
+ let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2);
+ let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2);
+ return (
+ <>
+ <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
+ style={{ strokeWidth: `${l.length / 2}` }}
+ x1={`${x1}`} y1={`${y1}`}
+ x2={`${x2}`} y2={`${y2}`} />
+ <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkCircle"
+ cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={5} onPointerDown={this.onPointerDown} />
+ </>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
new file mode 100644
index 000000000..30e158603
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
@@ -0,0 +1,12 @@
+.collectionfreeformlinksview-svgCanvas{
+ transform: translate(-10000px,-10000px);
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 20000px;
+ height: 20000px;
+ pointer-events: none;
+ }
+ .collectionfreeformlinksview-container {
+ pointer-events: none;
+ } \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
new file mode 100644
index 000000000..d5ce4e1e7
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -0,0 +1,130 @@
+import { computed, IReactionDisposer, reaction, trace } from "mobx";
+import { observer } from "mobx-react";
+import { Utils } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DocumentView } from "../../nodes/DocumentView";
+import { CollectionViewProps } from "../CollectionSubView";
+import "./CollectionFreeFormLinksView.scss";
+import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
+import React = require("react");
+import { Doc, DocListCastAsync, DocListCast } from "../../../../new_fields/Doc";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
+import { listSpec } from "../../../../new_fields/Schema";
+import { List } from "../../../../new_fields/List";
+import { Id } from "../../../../new_fields/RefField";
+
+@observer
+export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
+
+ _brushReactionDisposer?: IReactionDisposer;
+ componentDidMount() {
+ this._brushReactionDisposer = reaction(
+ () => {
+ let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
+ return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
+ },
+ () => {
+ let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
+ let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
+ views.forEach((dstDoc, i) => {
+ views.forEach((srcDoc, j) => {
+ let dstTarg = dstDoc;
+ let srcTarg = srcDoc;
+ let x1 = NumCast(srcDoc.x);
+ let x2 = NumCast(dstDoc.x);
+ let x1w = NumCast(srcDoc.width, -1);
+ let x2w = NumCast(dstDoc.width, -1);
+ if (x1w < 0 || x2w < 0 || i === j) { }
+ else {
+ let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
+ let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
+ return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
+ });
+ let brushAction = (field: (Doc | Promise<Doc>)[]) => {
+ let found = findBrush(field);
+ if (found !== -1) {
+ console.log("REMOVE BRUSH " + srcTarg.title + " " + dstTarg.title);
+ field.splice(found, 1);
+ }
+ };
+ if (Math.abs(x1 + x1w - x2) < 20) {
+ let linkDoc: Doc = new Doc();
+ linkDoc.title = "Histogram Brush";
+ linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
+ linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
+
+ brushAction = (field: (Doc | Promise<Doc>)[]) => {
+ if (findBrush(field) === -1) {
+ console.log("ADD BRUSH " + srcTarg.title + " " + dstTarg.title);
+ field.push(linkDoc);
+ }
+ };
+ }
+ let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []);
+ let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []);
+ if (dstBrushDocs === undefined) dstTarg.brushingDocs = dstBrushDocs = new List<Doc>();
+ else brushAction(dstBrushDocs);
+ if (srcBrushDocs === undefined) srcTarg.brushingDocs = srcBrushDocs = new List<Doc>();
+ else brushAction(srcBrushDocs);
+ }
+ });
+ });
+ });
+ }
+ componentWillUnmount() {
+ if (this._brushReactionDisposer) {
+ this._brushReactionDisposer();
+ }
+ }
+ documentAnchors(view: DocumentView) {
+ let equalViews = [view];
+ let containerDoc = FieldValue(Cast(view.props.Document.annotationOn, Doc));
+ if (containerDoc) {
+ equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.proto!);
+ }
+ if (view.props.ContainingCollectionView) {
+ let collid = view.props.ContainingCollectionView.props.Document[Id];
+ DocListCast(this.props.Document[this.props.fieldKey]).
+ filter(child =>
+ child[Id] === collid).map(view =>
+ DocumentManager.Instance.getDocumentViews(view).map(view =>
+ equalViews.push(view)));
+ }
+ return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document);
+ }
+
+ @computed
+ get uniqueConnections() {
+ let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
+ let srcViews = this.documentAnchors(connection.a);
+ let targetViews = this.documentAnchors(connection.b);
+ let possiblePairs: { a: Doc, b: Doc, }[] = [];
+ srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
+ possiblePairs.map(possiblePair =>
+ drawnPairs.reduce((found, drawnPair) => {
+ let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
+ if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) {
+ drawnPair.l.push(connection.l);
+ }
+ return match || found;
+ }, false)
+ ||
+ drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+ );
+ return drawnPairs;
+ }, [] as { a: Doc, b: Doc, l: Doc[] }[]);
+ return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l}
+ removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />);
+ }
+
+ render() {
+ return (
+ <div className="collectionfreeformlinksview-container">
+ <svg className="collectionfreeformlinksview-svgCanvas">
+ {this.uniqueConnections}
+ </svg>
+ {this.props.children}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
new file mode 100644
index 000000000..c5b8fc5e8
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss
@@ -0,0 +1,24 @@
+@import "globalCssVariables";
+
+.collectionFreeFormRemoteCursors-cont {
+
+ position:absolute;
+ z-index: $remoteCursors-zindex;
+ transform-origin: 'center center';
+}
+.collectionFreeFormRemoteCursors-canvas {
+
+ position:absolute;
+ width: 20px;
+ height: 20px;
+ opacity: 0.5;
+ border-radius: 50%;
+ border: 2px solid black;
+}
+.collectionFreeFormRemoteCursors-symbol {
+ font-size: 14;
+ color: black;
+ // fontStyle: "italic",
+ margin-left: -12;
+ margin-top: 4;
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
new file mode 100644
index 000000000..642118d75
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -0,0 +1,87 @@
+import { computed } from "mobx";
+import { observer } from "mobx-react";
+import { CollectionViewProps } from "../CollectionSubView";
+import "./CollectionFreeFormView.scss";
+import React = require("react");
+import v5 = require("uuid/v5");
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import CursorField from "../../../../new_fields/CursorField";
+import { List } from "../../../../new_fields/List";
+import { Cast } from "../../../../new_fields/Types";
+import { listSpec } from "../../../../new_fields/Schema";
+
+@observer
+export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
+
+ protected getCursors(): CursorField[] {
+ let doc = this.props.Document;
+
+ let id = CurrentUserUtils.id;
+ if (!id) {
+ return [];
+ }
+
+ let cursors = Cast(doc.cursors, listSpec(CursorField));
+
+ return (cursors || []).filter(cursor => cursor.data.metadata.id !== id);
+ }
+
+ private crosshairs?: HTMLCanvasElement;
+ drawCrosshairs = (backgroundColor: string) => {
+ if (this.crosshairs) {
+ let ctx = this.crosshairs.getContext('2d');
+ if (ctx) {
+ ctx.fillStyle = backgroundColor;
+ ctx.fillRect(0, 0, 20, 20);
+
+ ctx.fillStyle = "black";
+ ctx.lineWidth = 0.5;
+
+ ctx.beginPath();
+
+ ctx.moveTo(10, 0);
+ ctx.lineTo(10, 8);
+
+ ctx.moveTo(10, 20);
+ ctx.lineTo(10, 12);
+
+ ctx.moveTo(0, 10);
+ ctx.lineTo(8, 10);
+
+ ctx.moveTo(20, 10);
+ ctx.lineTo(12, 10);
+
+ ctx.stroke();
+
+ // ctx.font = "10px Arial";
+ // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
+ }
+ }
+ }
+
+ get sharedCursors() {
+ return this.getCursors().map(c => {
+ let m = c.data.metadata;
+ let l = c.data.position;
+ this.drawCrosshairs("#" + v5(m.id, v5.URL).substring(0, 6).toUpperCase() + "22");
+ return (
+ <div key={m.id} className="collectionFreeFormRemoteCursors-cont"
+ style={{ transform: `translate(${l.x - 10}px, ${l.y - 10}px)` }}
+ >
+ <canvas className="collectionFreeFormRemoteCursors-canvas"
+ ref={(el) => { if (el) this.crosshairs = el; }}
+ width={20}
+ height={20}
+ />
+ <p className="collectionFreeFormRemoteCursors-symbol">
+ {m.identifier[0].toUpperCase()}
+ </p>
+ </div>
+ );
+ });
+ }
+
+ render() {
+ return this.sharedCursors;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
new file mode 100644
index 000000000..063c9e2cf
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -0,0 +1,107 @@
+@import "../../globalCssVariables";
+
+.collectionfreeformview-ease {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transform-origin: left top;
+ transition: transform 1s;
+}
+
+.collectionfreeformview-none {
+ position: inherit;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transform-origin: left top;
+}
+
+.collectionfreeformview-container {
+ .collectionfreeformview>.jsx-parser {
+ position: inherit;
+ height: 100%;
+ width: 100%;
+ }
+
+ //nested freeform views
+ // .collectionfreeformview-container {
+ // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
+ // linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
+ // background-size: 30px 30px;
+ // }
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+ border: 0px solid $light-color-secondary;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ position: absolute;
+ .marqueeView {
+ overflow: hidden;
+ }
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+
+.collectionfreeformview-overlay {
+ .collectionfreeformview>.jsx-parser {
+ position: inherit;
+ height: 100%;
+ }
+
+ .formattedTextBox-cont {
+ background: $light-color-secondary;
+ overflow: visible;
+ }
+
+ opacity: 0.99;
+ border: 0px solid transparent;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ position:absolute;
+ .marqueeView {
+ overflow: hidden;
+ }
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+
+ .collectionfreeformview {
+ .formattedTextBox-cont {
+ background: yellow;
+ }
+ }
+}
+
+// selection border...?
+.border {
+ border-style: solid;
+ box-sizing: border-box;
+ width: 98%;
+ height: 98%;
+ border-radius: $border-radius;
+}
+
+//this is an animation for the blinking cursor!
+@keyframes blink {
+ 0% {
+ opacity: 0;
+ }
+
+ 49% {
+ opacity: 0;
+ }
+
+ 50% {
+ opacity: 1;
+ }
+}
+
+#prevCursor {
+ animation: blink 1s infinite;
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
new file mode 100644
index 000000000..ad32f70dc
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -0,0 +1,404 @@
+import { action, computed, trace } from "mobx";
+import { observer } from "mobx-react";
+import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
+import { DocumentManager } from "../../../util/DocumentManager";
+import { DragManager } from "../../../util/DragManager";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch } from "../../../util/UndoManager";
+import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
+import { InkingCanvas } from "../../InkingCanvas";
+import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
+import { DocumentContentsView } from "../../nodes/DocumentContentsView";
+import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
+import { CollectionSubView } from "../CollectionSubView";
+import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
+import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
+import "./CollectionFreeFormView.scss";
+import { MarqueeView } from "./MarqueeView";
+import React = require("react");
+import v5 = require("uuid/v5");
+import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
+import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
+import { FieldValue, Cast, NumCast, BoolCast } from "../../../../new_fields/Types";
+import { pageSchema } from "../../nodes/ImageBox";
+import { Id } from "../../../../new_fields/RefField";
+import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { HistoryUtil } from "../../../util/History";
+
+export const panZoomSchema = createSchema({
+ panX: "number",
+ panY: "number",
+ scale: "number"
+});
+
+type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>;
+const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema);
+
+@observer
+export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
+ public static RIGHT_BTN_DRAG = false;
+ private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
+ private _lastX: number = 0;
+ private _lastY: number = 0;
+ private get _pwidth() { return this.props.PanelWidth(); }
+ private get _pheight() { return this.props.PanelHeight(); }
+
+ @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
+ @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+ private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
+ private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
+ private panX = () => this.Document.panX || 0;
+ private panY = () => this.Document.panY || 0;
+ private zoomScaling = () => this.Document.scale || 1;
+ private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0; // shift so pan position is at center of window for non-overlay collections
+ private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
+ private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
+ private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
+ private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
+ private addLiveTextBox = (newBox: Doc) => {
+ this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ this.addDocument(newBox, false);
+ }
+ private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
+ this.props.addDocument(newBox, false);
+ this.bringToFront(newBox);
+ return true;
+ }
+ private selectDocuments = (docs: Doc[]) => {
+ SelectionManager.DeselectAll;
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
+ SelectionManager.SelectDoc(dv!, true));
+ }
+ public getActiveDocuments = () => {
+ const curPage = FieldValue(this.Document.curPage, -1);
+ return this.children.filter(doc => {
+ var page = NumCast(doc.page, -1);
+ return page === curPage || page === -1;
+ });
+ }
+
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
+ if (de.data.droppedDocuments.length) {
+ let dragDoc = de.data.droppedDocuments[0];
+ let zoom = NumCast(dragDoc.zoomBasis, 1);
+ let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+ let x = xp - de.data.xOffset / zoom;
+ let y = yp - de.data.yOffset / zoom;
+ let dropX = NumCast(de.data.droppedDocuments[0].x);
+ let dropY = NumCast(de.data.droppedDocuments[0].y);
+ de.data.droppedDocuments.map(d => {
+ d.x = x + NumCast(d.x) - dropX;
+ d.y = y + NumCast(d.y) - dropY;
+ if (!NumCast(d.width)) {
+ d.width = 300;
+ }
+ if (!NumCast(d.height)) {
+ let nw = NumCast(d.nativeWidth);
+ let nh = NumCast(d.nativeHeight);
+ d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;
+ }
+ this.bringToFront(d);
+ });
+ SelectionManager.ReselectAll();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent): void => {
+ if ((CollectionFreeFormView.RIGHT_BTN_DRAG &&
+ (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) ||
+ (e.button === 0 && e.altKey)) && this.props.active())) ||
+ (!CollectionFreeFormView.RIGHT_BTN_DRAG &&
+ ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && this.props.active()))) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ }
+ }
+
+ onPointerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ @action
+ onPointerMove = (e: PointerEvent): void => {
+ if (!e.cancelBubble) {
+ let x = this.Document.panX || 0;
+ let y = this.Document.panY || 0;
+ let docs = this.children || [];
+ let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+ if (!this.isAnnotationOverlay) {
+ let minx = docs.length ? NumCast(docs[0].x) : 0;
+ let maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
+ let miny = docs.length ? NumCast(docs[0].y) : 0;
+ let maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+ let ranges = docs.filter(doc => doc).reduce((range, doc) => {
+ let x = NumCast(doc.x);
+ let xe = x + NumCast(doc.width);
+ let y = NumCast(doc.y);
+ let ye = y + NumCast(doc.height);
+ return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
+ [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
+ }, [[minx, maxx], [miny, maxy]]);
+ let ink = Cast(this.props.Document.ink, InkField);
+ if (ink && ink.inkData) {
+ ink.inkData.forEach((value: StrokeData, key: string) => {
+ let bounds = InkingCanvas.StrokeRect(value);
+ ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)];
+ ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)];
+ });
+ }
+
+ let panelwidth = this._pwidth / this.zoomScaling() / 2;
+ let panelheight = this._pheight / this.zoomScaling() / 2;
+ if (x - dx < ranges[0][0] - panelwidth) x = ranges[0][1] + panelwidth + dx;
+ if (x - dx > ranges[0][1] + panelwidth) x = ranges[0][0] - panelwidth + dx;
+ if (y - dy < ranges[1][0] - panelheight) y = ranges[1][1] + panelheight + dy;
+ if (y - dy > ranges[1][1] + panelheight) y = ranges[1][0] - panelheight + dy;
+ }
+ this.setPan(x - dx, y - dy);
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onPointerWheel = (e: React.WheelEvent): void => {
+ // if (!this.props.active()) {
+ // return;
+ // }
+ let childSelected = this.children.some(doc => {
+ var dv = DocumentManager.Instance.getDocumentView(doc);
+ return dv && SelectionManager.IsSelected(dv) ? true : false;
+ });
+ if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) {
+ return;
+ }
+ e.stopPropagation();
+ const coefficient = 1000;
+
+ if (e.ctrlKey) {
+ let deltaScale = (1 - (e.deltaY / coefficient));
+ let nw = this.nativeWidth * deltaScale;
+ let nh = this.nativeHeight * deltaScale;
+ if (nw && nh) {
+ this.props.Document.nativeWidth = nw;
+ this.props.Document.nativeHeight = nh;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ } else {
+ // if (modes[e.deltaMode] === 'pixels') coefficient = 50;
+ // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
+ let deltaScale = (1 - (e.deltaY / coefficient));
+ if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
+ deltaScale = 1 / this.zoomScaling();
+ }
+ if (deltaScale < 0) deltaScale = -deltaScale;
+ let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);
+ let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
+
+ let safeScale = Math.abs(localTransform.Scale);
+ this.props.Document.scale = Math.abs(safeScale);
+ this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
+ e.stopPropagation();
+ }
+ }
+
+ @action
+ setPan(panX: number, panY: number) {
+ this.panDisposer && clearTimeout(this.panDisposer);
+ this.props.Document.panTransformType = "None";
+ var scale = this.getLocalTransform().inverse().Scale;
+ const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
+ const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
+ this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
+ this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+ }
+
+ @action
+ onDrop = (e: React.DragEvent): void => {
+ var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
+ super.onDrop(e, { x: pt[0], y: pt[1] });
+ }
+
+ onDragOver = (): void => {
+ }
+
+ bringToFront = (doc: Doc) => {
+ const docs = this.children;
+ docs.slice().sort((doc1, doc2) => {
+ if (doc1 === doc) return 1;
+ if (doc2 === doc) return -1;
+ return NumCast(doc1.zIndex) - NumCast(doc2.zIndex);
+ }).forEach((doc, index) => doc.zIndex = index + 1);
+ doc.zIndex = docs.length + 1;
+ }
+
+ panDisposer?: NodeJS.Timeout;
+ focusDocument = (doc: Doc) => {
+ const panX = this.Document.panX;
+ const panY = this.Document.panY;
+ const id = this.Document[Id];
+ const state = HistoryUtil.getState();
+ // TODO This technically isn't correct if type !== "doc", as
+ // currently nothing is done, but we should probably push a new state
+ if (state.type === "doc" && panX !== undefined && panY !== undefined) {
+ const init = state.initializers[id];
+ if (!init) {
+ state.initializers[id] = {
+ panX, panY
+ };
+ HistoryUtil.pushState(state);
+ } else if (init.panX !== panX || init.panY !== panY) {
+ init.panX = panX;
+ init.panY = panY;
+ HistoryUtil.pushState(state);
+ }
+ }
+ SelectionManager.DeselectAll();
+ const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
+ const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
+ const newState = HistoryUtil.getState();
+ newState.initializers[id] = { panX: newPanX, panY: newPanY };
+ HistoryUtil.pushState(newState);
+ this.setPan(newPanX, newPanY);
+ this.props.Document.panTransformType = "Ease";
+ this.props.focus(this.props.Document);
+ this.panDisposer = setTimeout(() => this.props.Document.panTransformType = "None", 2000); // wait 3 seconds, then reset to false
+ }
+
+
+ getDocumentViewProps(document: Doc): DocumentViewProps {
+ return {
+ Document: document,
+ toggleMinimized: emptyFunction,
+ addDocument: this.props.addDocument,
+ removeDocument: this.props.removeDocument,
+ moveDocument: this.props.moveDocument,
+ ScreenToLocalTransform: this.getTransform,
+ isTopMost: false,
+ selectOnLoad: document[Id] === this._selectOnLoaded,
+ PanelWidth: document[WidthSym],
+ PanelHeight: document[HeightSym],
+ ContentScaling: returnOne,
+ ContainingCollectionView: this.props.CollectionView,
+ focus: this.focusDocument,
+ parentActive: this.props.active,
+ whenActiveChanged: this.props.whenActiveChanged,
+ bringToFront: this.bringToFront,
+ addDocTab: this.props.addDocTab,
+ };
+ }
+
+ @computed.struct
+ get views() {
+ let curPage = FieldValue(this.Document.curPage, -1);
+ let docviews = this.children.reduce((prev, doc) => {
+ if (!(doc instanceof Doc)) return prev;
+ var page = NumCast(doc.page, -1);
+ if (page === curPage || page === -1) {
+ let minim = BoolCast(doc.isMinimized, false);
+ if (minim === undefined || !minim) {
+ prev.push(<CollectionFreeFormDocumentView key={doc[Id]} {...this.getDocumentViewProps(doc)} />);
+ }
+ }
+ return prev;
+ }, [] as JSX.Element[]);
+
+ setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
+
+ return docviews;
+ }
+
+ @action
+ onCursorMove = (e: React.PointerEvent) => {
+ super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
+ }
+
+ private childViews = () => [...this.views, <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />];
+ render() {
+ const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
+ const easing = () => this.props.Document.panTransformType === "Ease";
+ return (
+ <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
+ style={{ borderRadius: "inherit" }}
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
+ <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
+ addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
+ getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+ <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
+ easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+
+ <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+ {this.childViews}
+ </InkingCanvas>
+ </CollectionFreeFormLinksView>
+ <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+ </CollectionFreeFormViewPannableContents>
+ </MarqueeView>
+ <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} />
+ </div>
+ );
+ }
+}
+
+@observer
+class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
+ @computed get overlayView() {
+ return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
+ isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+ }
+ render() {
+ return this.overlayView;
+ }
+}
+
+@observer
+class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
+ @computed get backgroundView() {
+ return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
+ isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+ }
+ render() {
+ return this.props.Document.backgroundLayout ? this.backgroundView : (null);
+ }
+}
+
+interface CollectionFreeFormViewPannableContentsProps {
+ centeringShiftX: () => number;
+ centeringShiftY: () => number;
+ panX: () => number;
+ panY: () => number;
+ zoomScaling: () => number;
+ easing: () => boolean;
+}
+
+@observer
+class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
+ render() {
+ let freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none");
+ const cenx = this.props.centeringShiftX();
+ const ceny = this.props.centeringShiftY();
+ const panx = -this.props.panX();
+ const pany = -this.props.panY();
+ const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
+ return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+ {this.props.children}
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
new file mode 100644
index 000000000..6e8ec8662
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -0,0 +1,26 @@
+
+.marqueeView {
+ position: inherit;
+ top:0;
+ left:0;
+ width:100%;
+ height:100%;
+}
+.marquee {
+ border-style: dashed;
+ box-sizing: border-box;
+ position: absolute;
+ border-width: 1px;
+ border-color: black;
+ pointer-events: none;
+ .marquee-legend {
+ bottom:-18px;
+ left:0;
+ position: absolute;
+ font-size: 9;
+ white-space:nowrap;
+ }
+ .marquee-legend::after {
+ content: "Press: c (collection), s (summary), r (replace) or Delete"
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
new file mode 100644
index 000000000..c3c4115b8
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -0,0 +1,367 @@
+import * as htmlToImage from "html-to-image";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Docs } from "../../../documents/Documents";
+import { SelectionManager } from "../../../util/SelectionManager";
+import { Transform } from "../../../util/Transform";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { InkingCanvas } from "../../InkingCanvas";
+import { PreviewCursor } from "../../PreviewCursor";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./MarqueeView.scss";
+import React = require("react");
+import { Utils } from "../../../../Utils";
+import { Doc } from "../../../../new_fields/Doc";
+import { NumCast, Cast } from "../../../../new_fields/Types";
+import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { List } from "../../../../new_fields/List";
+import { ImageField } from "../../../../new_fields/URLField";
+import { Template, Templates } from "../../Templates";
+import { Gateway } from "../../../northstar/manager/Gateway";
+import { DocServer } from "../../../DocServer";
+import { Id } from "../../../../new_fields/RefField";
+
+interface MarqueeViewProps {
+ getContainerTransform: () => Transform;
+ getTransform: () => Transform;
+ container: CollectionFreeFormView;
+ addDocument: (doc: Doc, allowDuplicates: false) => boolean;
+ activeDocuments: () => Doc[];
+ selectDocuments: (docs: Doc[]) => void;
+ removeDocument: (doc: Doc) => boolean;
+ addLiveTextDocument: (doc: Doc) => void;
+ isSelected: () => boolean;
+}
+
+@observer
+export class MarqueeView extends React.Component<MarqueeViewProps>
+{
+ private _mainCont = React.createRef<HTMLDivElement>();
+ @observable _lastX: number = 0;
+ @observable _lastY: number = 0;
+ @observable _downX: number = 0;
+ @observable _downY: number = 0;
+ @observable _visible: boolean = false;
+ _commandExecuted = false;
+
+ @action
+ cleanupInteractions = (all: boolean = false) => {
+ if (all) {
+ document.removeEventListener("pointerup", this.onPointerUp, true);
+ document.removeEventListener("pointermove", this.onPointerMove, true);
+ }
+ document.removeEventListener("keydown", this.marqueeCommand, true);
+ this._visible = false;
+ }
+
+ @undoBatch
+ @action
+ onKeyPress = (e: KeyboardEvent) => {
+ //make textbox and add it to this collection
+ let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
+ if (e.key === "q" && e.ctrlKey) {
+ e.preventDefault();
+ (async () => {
+ let text: string = await navigator.clipboard.readText();
+ let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
+ for (let i = 0; i < ns.length - 1; i++) {
+ while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
+ ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
+ ns[i].endsWith(".\r") || ns[i].endsWith(".") ||
+ ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) {
+ let sub = ns[i].endsWith("\r") ? 1 : 0;
+ let br = ns[i + 1].trim() === "";
+ ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft());
+ if (br) break;
+ }
+ }
+ ns.map(line => {
+ let indent = line.search(/\S|$/);
+ let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
+ this.props.addDocument(newBox, false);
+ y += 40 * this.props.getTransform().Scale;
+ });
+ })();
+ } else if (e.key === "b" && e.ctrlKey) {
+ //heuristically converts pasted text into a table.
+ // assumes each entry is separated by a tab
+ // skips all rows until it gets to a row with more than one entry
+ // assumes that 1st row has header entry for each column
+ // assumes subsequent rows have entries for each column header OR
+ // any row that has only one column is a section header-- this header is then added as a column to subsequent rows until the next header
+ // assumes each cell is a string or a number
+ e.preventDefault();
+ (async () => {
+ let text: string = await navigator.clipboard.readText();
+ let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
+ while (ns.length > 0 && ns[0].split("\t").length < 2) {
+ ns.splice(0, 1);
+ }
+ if (ns.length > 0) {
+ let columns = ns[0].split("\t");
+ let docList: Doc[] = [];
+ let groupAttr: string | number = "";
+ for (let i = 1; i < ns.length - 1; i++) {
+ let values = ns[i].split("\t");
+ if (values.length === 1 && columns.length > 1) {
+ groupAttr = values[0];
+ continue;
+ }
+ let doc = new Doc();
+ columns.forEach((col, i) => doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
+ if (groupAttr) {
+ doc._group = groupAttr;
+ }
+ doc.title = i.toString();
+ docList.push(doc);
+ }
+ let newCol = Docs.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+
+ this.props.addDocument(newCol, false);
+ }
+ })();
+ } else {
+ let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
+ this.props.addLiveTextDocument(newBox);
+ }
+ e.stopPropagation();
+ }
+ @action
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = this._lastX = e.pageX;
+ this._downY = this._lastY = e.pageY;
+ this._commandExecuted = false;
+ PreviewCursor.Visible = false;
+ if ((CollectionFreeFormView.RIGHT_BTN_DRAG && e.button === 0 && !e.altKey && !e.metaKey && this.props.container.props.active()) ||
+ (!CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && this.props.container.props.active())) {
+ document.addEventListener("pointermove", this.onPointerMove, true);
+ document.addEventListener("pointerup", this.onPointerUp, true);
+ document.addEventListener("keydown", this.marqueeCommand, true);
+ // bcz: do we need this? it kills the context menu on the main collection
+ // e.stopPropagation();
+ }
+ if (e.altKey) {
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onPointerMove = (e: PointerEvent): void => {
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ if (!e.cancelBubble) {
+ if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD ||
+ Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
+ if (!this._commandExecuted) {
+ this._visible = true;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+ if (e.altKey) {
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent): void => {
+ if (this._visible) {
+ let mselect = this.marqueeSelect();
+ if (!e.shiftKey) {
+ SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
+ }
+ this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
+ }
+ this.cleanupInteractions(true);
+ if (e.altKey) {
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onClick = (e: React.MouseEvent): void => {
+ if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress);
+ // let the DocumentView stopPropagation of this event when it selects this document
+ } else { // why do we get a click event when the cursor have moved a big distance?
+ // let's cut it off here so no one else has to deal with it.
+ e.stopPropagation();
+ }
+ }
+
+ intersectRect(r1: { left: number, top: number, width: number, height: number },
+ r2: { left: number, top: number, width: number, height: number }) {
+ return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+ }
+
+ @computed
+ get Bounds() {
+ let left = this._downX < this._lastX ? this._downX : this._lastX;
+ let top = this._downY < this._lastY ? this._downY : this._lastY;
+ let topLeft = this.props.getTransform().transformPoint(left, top);
+ let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
+ }
+
+ @undoBatch
+ @action
+ marqueeCommand = async (e: KeyboardEvent) => {
+ if (this._commandExecuted || (e as any).propagationIsStopped) {
+ return;
+ }
+ if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
+ this._commandExecuted = true;
+ e.stopPropagation();
+ (e as any).propagationIsStopped = true;
+ this.marqueeSelect().map(d => this.props.removeDocument(d));
+ let ink = Cast(this.props.container.props.Document.ink, InkField);
+ if (ink) {
+ this.marqueeInkDelete(ink.inkData);
+ }
+ SelectionManager.DeselectAll();
+ this.cleanupInteractions(false);
+ e.stopPropagation();
+ }
+ if (e.key === "c" || e.key === "s" || e.key === "e" || e.key === "p") {
+ this._commandExecuted = true;
+ e.stopPropagation();
+ (e as any).propagationIsStopped = true;
+ let bounds = this.Bounds;
+ let selected = this.marqueeSelect();
+ if (e.key === "c") {
+ selected.map(d => {
+ this.props.removeDocument(d);
+ d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+ d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+ d.page = -1;
+ return d;
+ });
+ }
+ let ink = Cast(this.props.container.props.Document.ink, InkField);
+ let inkData = ink ? ink.inkData : undefined;
+ let zoomBasis = NumCast(this.props.container.props.Document.scale, 1);
+ let newCollection = Docs.FreeformDocument(selected, {
+ x: bounds.left,
+ y: bounds.top,
+ panX: 0,
+ panY: 0,
+ borderRounding: e.key === "e" ? -1 : undefined,
+ scale: zoomBasis,
+ width: bounds.width * zoomBasis,
+ height: bounds.height * zoomBasis,
+ ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined,
+ title: e.key === "s" ? "-summary-" : e.key === "p" ? "-summary-" : "a nested collection",
+ });
+ this.marqueeInkDelete(inkData);
+
+ if (e.key === "s" || e.key === "p") {
+
+ htmlToImage.toPng(this._mainCont.current!, { width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, quality: 1 }).then((dataUrl) => {
+ selected.map(d => {
+ this.props.removeDocument(d);
+ d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+ d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+ d.page = -1;
+ return d;
+ });
+ let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+ summary.proto!.thumbnail = new ImageField(new URL(dataUrl));
+ summary.proto!.templates = new List<string>([Templates.ImageOverlay(Math.min(50, bounds.width), bounds.height * Math.min(50, bounds.width) / bounds.width, "thumbnail")]);
+ newCollection.proto!.summaryDoc = summary;
+ selected = [newCollection];
+ newCollection.x = bounds.left + bounds.width;
+ this.props.addDocument(newCollection, false);
+ summary.proto!.summarizedDocs = new List<Doc>(selected.map(s => s.proto!));
+ //summary.proto!.maximizeOnRight = true;
+ //summary.proto!.isButton = true;
+ //let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
+ // selected.map(summarizedDoc => {
+ // let maxx = NumCast(summarizedDoc.x, undefined);
+ // let maxy = NumCast(summarizedDoc.y, undefined);
+ // let maxw = NumCast(summarizedDoc.width, undefined);
+ // let maxh = NumCast(summarizedDoc.height, undefined);
+ // summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0]);
+ // });
+ this.props.addLiveTextDocument(summary);
+ });
+ }
+ else {
+ this.props.addDocument(newCollection, false);
+ SelectionManager.DeselectAll();
+ this.props.selectDocuments([newCollection]);
+ }
+ this.cleanupInteractions(false);
+ }
+ }
+ @action
+ marqueeInkSelect(ink: Map<any, any>) {
+ let idata = new Map();
+ let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
+ let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
+ ink.forEach((value: StrokeData, key: string, map: any) => {
+ if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
+ idata.set(key,
+ {
+ pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })),
+ color: value.color,
+ width: value.width,
+ tool: value.tool,
+ page: -1
+ });
+ }
+ });
+ return idata;
+ }
+
+ @action
+ marqueeInkDelete(ink?: Map<any, any>) {
+ // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
+ // ink.forEach((value: StrokeData, key: string, map: any) =>
+ // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
+
+ if (ink) {
+ let idata = new Map();
+ ink.forEach((value: StrokeData, key: string, map: any) =>
+ !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
+ Doc.SetOnPrototype(this.props.container.props.Document, "ink", new InkField(idata));
+ }
+ }
+
+ marqueeSelect() {
+ let selRect = this.Bounds;
+ let selection: Doc[] = [];
+ this.props.activeDocuments().map(doc => {
+ var z = NumCast(doc.zoomBasis, 1);
+ var x = NumCast(doc.x);
+ var y = NumCast(doc.y);
+ var w = NumCast(doc.width) / z;
+ var h = NumCast(doc.height) / z;
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
+ selection.push(doc);
+ }
+ });
+ return selection;
+ }
+
+ @computed
+ get marqueeDiv() {
+ let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ return <div className="marquee" style={{ width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} >
+ <span className="marquee-legend" />
+ </div>;
+ }
+
+ render() {
+ let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
+ return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+ <div style={{ position: "relative", transform: `translate(${p[0]}px, ${p[1]}px)` }} >
+ {!this._visible ? null : this.marqueeDiv}
+ <div ref={this._mainCont} style={{ transform: `translate(${-p[0]}px, ${-p[1]}px)` }} >
+ {this.props.children}
+ </div>
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
new file mode 100644
index 000000000..838d4d9ac
--- /dev/null
+++ b/src/client/views/globalCssVariables.scss
@@ -0,0 +1,35 @@
+@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
+// colors
+$light-color: #fcfbf7;
+$light-color-secondary:#f1efeb;
+//$main-accent: #61aaa3;
+$main-accent: #aaaaa3;
+// $alt-accent: #cdd5ec;
+// $alt-accent: #cdeceb;
+//$alt-accent: #59dff7;
+$alt-accent: #c2c2c5;
+$lighter-alt-accent: rgb(207, 220, 240);
+$intermediate-color: #9c9396;
+$dark-color: #121721;
+// fonts
+$sans-serif: "Noto Sans", sans-serif;
+// $sans-serif: "Roboto Slab", sans-serif;
+$serif: "Crimson Text", serif;
+// misc values
+$border-radius: 0.3em;
+//
+
+ // dragged items
+$contextMenu-zindex: 1000; // context menu shows up over everything
+$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
+$docDecorations-zindex: 998; // then doc decorations appear over everything else
+$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
+$COLLECTION_BORDER_WIDTH: 1;
+$MINIMIZED_ICON_SIZE:25;
+$MAX_ROW_HEIGHT: 44px;
+:export {
+ contextMenuZindex: $contextMenu-zindex;
+ COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
+ MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
+ MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
+} \ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
new file mode 100644
index 000000000..9788d31f7
--- /dev/null
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -0,0 +1,10 @@
+
+interface IGlobalScss {
+ contextMenuZindex: string; // context menu shows up over everything
+ COLLECTION_BORDER_WIDTH: string;
+ MINIMIZED_ICON_SIZE: string;
+ MAX_ROW_HEIGHT: string;
+}
+declare const globalCssVariables: IGlobalScss;
+
+export = globalCssVariables; \ No newline at end of file
diff --git a/src/client/views/nodes/Annotation.tsx b/src/client/views/nodes/Annotation.tsx
index a2c7be1a8..3e4ed6bf1 100644
--- a/src/client/views/nodes/Annotation.tsx
+++ b/src/client/views/nodes/Annotation.tsx
@@ -1,16 +1,16 @@
import "./ImageBox.scss";
-import React = require("react")
-import { observer } from "mobx-react"
+import React = require("react");
+import { observer } from "mobx-react";
import { observable, action } from 'mobx';
-import 'react-pdf/dist/Page/AnnotationLayer.css'
+import 'react-pdf/dist/Page/AnnotationLayer.css';
-interface IProps{
+interface IProps {
Span: HTMLSpanElement;
- X: number;
- Y: number;
- Highlights: any[];
- Annotations: any[];
- CurrAnno: any[];
+ X: number;
+ Y: number;
+ Highlights: any[];
+ Annotations: any[];
+ CurrAnno: any[];
}
@@ -23,95 +23,95 @@ interface IProps{
*/
@observer
export class Annotation extends React.Component<IProps> {
-
+
/**
* changes color of the span (highlighted section)
*/
- onColorChange = (e:React.PointerEvent) => {
- if (e.currentTarget.innerHTML == "r"){
- this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)"
- } else if (e.currentTarget.innerHTML == "b"){
- this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)"
- } else if (e.currentTarget.innerHTML == "y"){
- this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)"
- } else if (e.currentTarget.innerHTML == "g"){
- this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)"
+ onColorChange = (e: React.PointerEvent) => {
+ if (e.currentTarget.innerHTML === "r") {
+ this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)";
+ } else if (e.currentTarget.innerHTML === "b") {
+ this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)";
+ } else if (e.currentTarget.innerHTML === "y") {
+ this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)";
+ } else if (e.currentTarget.innerHTML === "g") {
+ this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)";
}
-
+
}
/**
* removes the highlighted span. Supposed to remove Annotation too, but I don't know how to unmount this
*/
@action
- onRemove = (e:any) => {
- let index:number = -1;
+ onRemove = (e: any) => {
+ let index: number = -1;
//finding the highlight in the highlight array
this.props.Highlights.forEach((e) => {
- for (let i = 0; i < e.spans.length; i++){
- if (e.spans[i] == this.props.Span){
- index = this.props.Highlights.indexOf(e);
- this.props.Highlights.splice(index, 1);
+ for (const span of e.spans) {
+ if (span === this.props.Span) {
+ index = this.props.Highlights.indexOf(e);
+ this.props.Highlights.splice(index, 1);
}
}
- })
+ });
//removing from CurrAnno and Annotation array
- this.props.Annotations.splice(index, 1);
- this.props.CurrAnno.pop()
-
+ this.props.Annotations.splice(index, 1);
+ this.props.CurrAnno.pop();
+
//removing span from div
- if(this.props.Span.parentElement){
- let nodesArray = this.props.Span.parentElement.childNodes;
+ if (this.props.Span.parentElement) {
+ let nodesArray = this.props.Span.parentElement.childNodes;
nodesArray.forEach((e) => {
- if (e == this.props.Span){
- if (this.props.Span.parentElement){
+ if (e === this.props.Span) {
+ if (this.props.Span.parentElement) {
this.props.Highlights.forEach((item) => {
- if (item == e){
- item.remove();
+ if (item === e) {
+ item.remove();
}
- })
- e.remove();
+ });
+ e.remove();
}
}
- })
+ });
}
-
-
+
+
}
render() {
return (
- <div
- style = {{
- position: "absolute",
- top: "20px",
- left: "0px",
- zIndex: 1,
- transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
-
- }}>
- <div style = {{width:"200px", height:"50px", backgroundColor: "orange"}}>
+ <div
+ style={{
+ position: "absolute",
+ top: "20px",
+ left: "0px",
+ zIndex: 1,
+ transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
+
+ }}>
+ <div style={{ width: "200px", height: "50px", backgroundColor: "orange" }}>
<button
- style = {{borderRadius: "25px", width:"25%", height:"100%"}}
- onClick = {this.onRemove}
+ style={{ borderRadius: "25px", width: "25%", height: "100%" }}
+ onClick={this.onRemove}
>x</button>
- <div style = {{width:"75%", height: "100%" , display:"inline-block"}}>
- <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"red", borderRadius:"50%", color: "transparent"}}>r</button>
- <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"blue", borderRadius:"50%", color: "transparent"}}>b</button>
- <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"yellow", borderRadius:"50%", color:"transparent"}}>y</button>
- <button onPointerDown = {this.onColorChange} style = {{backgroundColor:"green", borderRadius:"50%", color:"transparent"}}>g</button>
+ <div style={{ width: "75%", height: "100%", display: "inline-block" }}>
+ <button onPointerDown={this.onColorChange} style={{ backgroundColor: "red", borderRadius: "50%", color: "transparent" }}>r</button>
+ <button onPointerDown={this.onColorChange} style={{ backgroundColor: "blue", borderRadius: "50%", color: "transparent" }}>b</button>
+ <button onPointerDown={this.onColorChange} style={{ backgroundColor: "yellow", borderRadius: "50%", color: "transparent" }}>y</button>
+ <button onPointerDown={this.onColorChange} style={{ backgroundColor: "green", borderRadius: "50%", color: "transparent" }}>g</button>
</div>
-
+
</div>
- <div style = {{width:"200px", height:"200"}}>
- <textarea style = {{width: "100%", height: "100%"}}
- defaultValue = "Enter Text Here..."
-
+ <div style={{ width: "200px", height: "200" }}>
+ <textarea style={{ width: "100%", height: "100%" }}
+ defaultValue="Enter Text Here..."
+
></textarea>
</div>
</div>
-
+
);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index f7d89843d..be12dced3 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,44 +1,27 @@
-import React = require("react")
+import React = require("react");
import { FieldViewProps, FieldView } from './FieldView';
-import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
-import { ContextMenu } from "../../views/ContextMenu";
-import { observable, action } from 'mobx';
-import { KeyStore } from '../../../fields/KeyStore';
-import { AudioField } from "../../../fields/AudioField";
-import "./AudioBox.scss"
-import { NumberField } from "../../../fields/NumberField";
+import { observer } from "mobx-react";
+import "./AudioBox.scss";
+import { Cast } from "../../../new_fields/Types";
+import { AudioField } from "../../../new_fields/URLField";
+const defaultField: AudioField = new AudioField(new URL("http://techslides.com/demos/samples/sample.mp3"));
@observer
export class AudioBox extends React.Component<FieldViewProps> {
- public static LayoutString() { return FieldView.LayoutString(AudioBox) }
+ public static LayoutString() { return FieldView.LayoutString(AudioBox); }
- constructor(props: FieldViewProps) {
- super(props);
- }
-
-
-
- componentDidMount() {
- }
-
- componentWillUnmount() {
- }
-
-
render() {
- let field = this.props.doc.Get(this.props.fieldKey)
- let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3":
- field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
-
+ let field = Cast(this.props.Document[this.props.fieldKey], AudioField, defaultField);
+ let path = field.url.href;
+
return (
<div>
- <audio controls className = "audiobox-cont">
- <source src = {path} type="audio/mpeg"/>
+ <audio controls className="audiobox-cont">
+ <source src={path} type="audio/mpeg" />
Not supported.
</audio>
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 50dc5a619..a0efc3154 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,84 +1,288 @@
-import { computed, trace } from "mobx";
+import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
-import { KeyStore } from "../../../fields/KeyStore";
-import { NumberField } from "../../../fields/NumberField";
+import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { OmitKeys, Utils } from "../../../Utils";
+import { DocumentManager } from "../../util/DocumentManager";
+import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
-import { DocumentView, DocumentViewProps } from "./DocumentView";
+import { UndoManager } from "../../util/UndoManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { DocComponent } from "../DocComponent";
+import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");
+export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
+}
+
+const schema = createSchema({
+ zoomBasis: "number",
+ zIndex: "number",
+});
+
+//TODO Types: The import order is wrong, so positionSchema is undefined
+type FreeformDocument = makeInterface<[typeof schema, typeof positionSchema]>;
+const FreeformDocument = makeInterface(schema, positionSchema);
@observer
-export class CollectionFreeFormDocumentView extends React.Component<DocumentViewProps> {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
private _mainCont = React.createRef<HTMLDivElement>();
+ private _downX: number = 0;
+ private _downY: number = 0;
+ _bringToFrontDisposer?: IReactionDisposer;
- constructor(props: DocumentViewProps) {
- super(props);
- }
- get screenRect(): ClientRect | DOMRect {
- if (this._mainCont.current) {
- return this._mainCont.current.getBoundingClientRect();
- }
- return new DOMRect();
- }
-
- @computed
- get transform(): string {
- return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px)`;
+ @computed get transform() {
+ return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `;
}
- @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
- @computed get width(): number { return this.props.Document.Width(); }
- @computed get height(): number { return this.props.Document.Height(); }
- @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
- @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+ @computed get X() { return FieldValue(this.Document.x, 0); }
+ @computed get Y() { return FieldValue(this.Document.y, 0); }
+ @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); }
+ @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); }
+ @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); }
+ @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.width, 0); }
+ @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.height, 0); }
set width(w: number) {
- this.props.Document.SetData(KeyStore.Width, w, NumberField)
+ this.Document.width = w;
if (this.nativeWidth && this.nativeHeight) {
- this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w)
+ this.Document.height = this.nativeHeight / this.nativeWidth * w;
}
}
-
set height(h: number) {
- this.props.Document.SetData(KeyStore.Height, h, NumberField);
+ this.Document.height = h;
if (this.nativeWidth && this.nativeHeight) {
- this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h)
+ this.Document.width = this.nativeWidth / this.nativeHeight * h;
}
}
+ contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
+ panelWidth = () => this.props.PanelWidth();
+ panelHeight = () => this.props.PanelHeight();
+ toggleMinimized = async () => this.toggleIcon(await DocListCastAsync(this.props.Document.maximizedDocs));
+ getTransform = (): Transform => this.props.ScreenToLocalTransform()
+ .translate(-this.X, -this.Y)
+ .scale(1 / this.contentScaling()).scale(1 / this.zoom)
- set zIndex(h: number) {
- this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
+ @computed
+ get docView() {
+ return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit}
+ toggleMinimized={this.toggleMinimized}
+ ContentScaling={this.contentScaling}
+ ScreenToLocalTransform={this.getTransform}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ />;
}
- contentScaling = () => {
- return this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
+ componentDidMount() {
+ this._bringToFrontDisposer = reaction(() => this.props.Document.isIconAnimating, (values) => {
+ this.props.bringToFront(this.props.Document);
+ if (values instanceof List) {
+ let scrpt = this.props.ScreenToLocalTransform().transformPoint(values[0], values[1]);
+ this.animateBetweenIcon(true, scrpt, [this.Document.x || 0, this.Document.y || 0],
+ this.Document.width || 0, this.Document.height || 0, values[2], values[3] ? true : false);
+ }
+ }, { fireImmediately: true });
}
- getTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().
- translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0)).scale(1 / this.contentScaling());
+ componentWillUnmount() {
+ if (this._bringToFrontDisposer) this._bringToFrontDisposer();
}
- @computed
- get docView() {
- return <DocumentView {...this.props}
- ContentScaling={this.contentScaling}
- ScreenToLocalTransform={this.getTransform}
- />
+ animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, maximizing: boolean) {
+
+ setTimeout(() => {
+ let now = Date.now();
+ let progress = Math.min(1, (now - stime) / 200);
+ let pval = maximizing ?
+ [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] :
+ [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress];
+ this.props.Document.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress;
+ this.props.Document.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress;
+ this.props.Document.x = pval[0];
+ this.props.Document.y = pval[1];
+ if (first) {
+ this.props.Document.proto!.willMaximize = false;
+ }
+ if (now < stime + 200) {
+ this.animateBetweenIcon(false, icon, targ, width, height, stime, maximizing);
+ }
+ else {
+ if (!maximizing) {
+ this.props.Document.proto!.isMinimized = true;
+ this.props.Document.x = targ[0];
+ this.props.Document.y = targ[1];
+ this.props.Document.width = width;
+ this.props.Document.height = height;
+ }
+ this.props.Document.proto!.isIconAnimating = undefined;
+ }
+ },
+ 2);
+ }
+ @action
+ public toggleIcon = async (maximizedDocs: Doc[] | undefined): Promise<void> => {
+ SelectionManager.DeselectAll();
+ let isMinimized: boolean | undefined;
+ let minimizedDoc: Doc | undefined = this.props.Document;
+ if (!maximizedDocs) {
+ minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
+ if (minimizedDoc) maximizedDocs = await DocListCastAsync(minimizedDoc.maximizedDocs);
+ }
+ if (minimizedDoc && maximizedDocs) {
+ let minimizedTarget = minimizedDoc;
+ if (!CollectionFreeFormDocumentView._undoBatch) {
+ CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
+ }
+ maximizedDocs.map(maximizedDoc => {
+ let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
+ if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
+ if (isMinimized === undefined) {
+ isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+ }
+ let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2;
+ let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2;
+ if (minx !== undefined && miny !== undefined) {
+ let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
+ maximizedDoc.willMaximize = isMinimized;
+ maximizedDoc.isMinimized = false;
+ maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
+ }
+ }
+ });
+ setTimeout(() => {
+ CollectionFreeFormDocumentView._undoBatch && CollectionFreeFormDocumentView._undoBatch.end();
+ CollectionFreeFormDocumentView._undoBatch = undefined;
+ }, 500);
+ }
+ }
+ private _lastTap: number = 0;
+ static _undoBatch?: UndoManager.Batch = undefined;
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if (e.button === 0 && e.altKey) {
+ e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
+ } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document
+ if (Date.now() - this._lastTap < 300) {
+ if (e.buttons === 1) {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ } else {
+ this._lastTap = Date.now();
+ }
+ }
+ onPointerUp = (e: PointerEvent): void => {
+
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
+ this.props.addDocTab(this.props.Document);
+ }
+ e.stopPropagation();
+ }
+ onClick = async (e: React.MouseEvent) => {
+ e.stopPropagation();
+ let altKey = e.altKey;
+ if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ let isExpander = (e.target as any).id === "isExpander";
+ if (BoolCast(this.props.Document.isButton, false) || isExpander) {
+ let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
+ let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
+ let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
+ let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
+ let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
+ let expandedDocs: Doc[] = [];
+ expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
+ expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
+ expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
+ // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []),
+ // ...(maximizedDocs ? maximizedDocs : []),
+ // ...(summarizedDocs ? summarizedDocs : []),];
+ if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents
+ let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedDocs[0], this.props.ContainingCollectionView);
+ if (!hasView && ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight))) {
+ let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
+ if (dataDocs) {
+ SelectionManager.DeselectAll();
+ expandedDocs.forEach(maxDoc => {
+ maxDoc.isMinimized = false;
+ if (!CollectionDockingView.Instance.CloseRightSplit(maxDoc)) {
+ CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(maxDoc));
+ }
+ });
+ }
+ } else {
+ //if (altKey) this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(maxDoc, false));
+ this.toggleIcon(expandedDocs);
+ }
+ }
+ else if (linkedToDocs.length || linkedFromDocs.length) {
+ SelectionManager.DeselectAll();
+ let linkedFwdDocs = [
+ linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
+ linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
+ if (linkedFwdDocs) {
+ DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], altKey);
+ }
+ }
+ }
+ }
+ }
+
+ onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
+ onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
+
+ borderRounding = () => {
+ let br = NumCast(this.props.Document.borderRounding);
+ return br >= 0 ? br :
+ NumCast(this.props.Document.nativeWidth) === 0 ?
+ Math.min(this.props.PanelWidth(), this.props.PanelHeight())
+ : Math.min(this.Document.nativeWidth || 0, this.Document.nativeHeight || 0);
}
render() {
+ let maximizedDoc = FieldValue(Cast(this.props.Document.maximizedDocs, listSpec(Doc)));
+ let zoomFade = 1;
+ //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1);
+ let transform = this.getTransform().scale(this.contentScaling()).inverse();
+ var [sptX, sptY] = transform.transformPoint(0, 0);
+ let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
+ let w = bptX - sptX;
+ //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
+ const screenWidth = Math.min(50 * NumCast(this.props.Document.nativeWidth, 0), 1800);
+ let fadeUp = .75 * screenWidth;
+ let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth;
+ zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? Math.sqrt(Math.sqrt(fadeDown / w)) : w / fadeUp))) : 1;
+
return (
- <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
- transformOrigin: "left top",
- transform: this.transform,
- width: this.width,
- height: this.height,
- position: "absolute",
- zIndex: this.zIndex,
- backgroundColor: "transparent"
- }} >
+ <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
+ onPointerDown={this.onPointerDown}
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter}
+ onClick={this.onClick}
+ style={{
+ outlineColor: "maroon",
+ outlineStyle: "dashed",
+ outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ||
+ BoolCast(this.props.Document.protoBrush, false) ?
+ `${1 * this.getTransform().Scale}px` : "0px",
+ opacity: zoomFade,
+ borderRadius: `${this.borderRounding()}px`,
+ transformOrigin: "left top",
+ transform: this.transform,
+ pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
+ width: this.width,
+ height: this.height,
+ position: "absolute",
+ zIndex: this.Document.zIndex || 0,
+ backgroundColor: "transparent"
+ }} >
{this.docView}
</div>
);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
new file mode 100644
index 000000000..d2cb11586
--- /dev/null
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -0,0 +1,111 @@
+import { computed, trace } from "mobx";
+import { observer } from "mobx-react";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { CollectionView } from "../collections/CollectionView";
+import { AudioBox } from "./AudioBox";
+import { DocumentViewProps } from "./DocumentView";
+import "./DocumentView.scss";
+import { FormattedTextBox } from "./FormattedTextBox";
+import { ImageBox } from "./ImageBox";
+import { IconBox } from "./IconBox";
+import { KeyValueBox } from "./KeyValueBox";
+import { PDFBox } from "./PDFBox";
+import { VideoBox } from "./VideoBox";
+import { FieldView } from "./FieldView";
+import { WebBox } from "./WebBox";
+import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
+import React = require("react");
+import { FieldViewProps } from "./FieldView";
+import { Without, OmitKeys } from "../../../Utils";
+import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+
+type BindingProps = Without<FieldViewProps, 'fieldKey'>;
+export interface JsxBindings {
+ props: BindingProps;
+}
+
+class ObserverJsxParser1 extends JsxParser {
+ constructor(props: any) {
+ super(props);
+ observer(this as any);
+ }
+}
+
+const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
+
+@observer
+export class DocumentContentsView extends React.Component<DocumentViewProps & {
+ isSelected: () => boolean,
+ select: (ctrl: boolean) => void,
+ layoutKey: string,
+}> {
+ @computed get layout(): string {
+ const layout = Cast(this.props.Document[this.props.layoutKey], "string");
+ if (layout === undefined) {
+ return this.props.Document.data ?
+ "<FieldView {...props} fieldKey='data' />" :
+ KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : "");
+ } else if (typeof layout === "string") {
+ return layout;
+ } else {
+ return "<p>Loading layout</p>";
+ }
+ }
+
+ CreateBindings(): JsxBindings {
+ return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
+ }
+
+ @computed get templates(): List<string> {
+ let field = this.props.Document.templates;
+ if (field && field instanceof List) {
+ return field;
+ }
+ return new List<string>();
+ }
+ @computed get finalLayout() {
+ const baseLayout = this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout;
+ let base = baseLayout;
+ let layout = baseLayout;
+
+ // bcz: templates are intended only for a document's primary layout or overlay (not background). However,
+ // a DocumentContentsView is used to render annotation overlays, so we detect that here
+ // by checking the layoutKey. This should probably be moved into
+ // a prop so that the overlay can explicitly turn off templates.
+ if ((this.props.layoutKey === "overlayLayout" && StrCast(this.props.Document.layout).indexOf("CollectionView") !== -1) ||
+ (this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) {
+ this.templates.forEach(template => {
+ let self = this;
+ // this scales constants in the markup by the scaling applied to the document, but caps the constants to be smaller
+ // than the width/height of the containing document
+ function convertConstantsToNative(match: string, offset: number, x: string) {
+ let px = Number(match.replace("px", ""));
+ return `${Math.min(NumCast(self.props.Document.height, 0),
+ Math.min(NumCast(self.props.Document.width, 0),
+ px * self.props.ScreenToLocalTransform().Scale))}px`;
+ }
+ let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative);
+ layout = nativizedTemplate.replace("{layout}", base);
+ base = layout;
+ });
+ }
+ return layout;
+ }
+
+ render() {
+ if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
+ return <ObserverJsxParser
+ components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+ bindings={this.CreateBindings()}
+ jsx={this.finalLayout}
+ showWarnings={true}
+ onError={(test: any) => { console.log(test); }}
+ />;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index ab913897b..7c72fb6e6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,23 +1,33 @@
-.documentView-node {
- position: absolute;
- background: #cdcdcd;
- //overflow: hidden;
- &.minimized {
- width: 30px;
- height: 30px;
- }
- .top {
- background: #232323;
- height: 20px;
- cursor: pointer;
- }
- .content {
- padding: 20px 20px;
- height: auto;
- box-sizing: border-box;
- }
- .scroll-box {
- overflow-y: scroll;
- height: calc(100% - 20px);
- }
+@import "../globalCssVariables";
+
+.documentView-node, .documentView-node-topmost {
+ position: inherit;
+ top: 0;
+ left:0;
+ // background: $light-color; //overflow: hidden;
+ transform-origin: left top;
+
+ &.minimized {
+ width: 30px;
+ height: 30px;
+ }
+
+ .top {
+ height: 20px;
+ cursor: pointer;
+ }
+
+ .content {
+ padding: 20px 20px;
+ height: auto;
+ box-sizing: border-box;
+ }
+
+ .scroll-box {
+ overflow-y: scroll;
+ height: calc(100% - 20px);
+ }
+}
+.documentView-node-topmost {
+ background: white;
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 907762837..d690893ef 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,40 +1,36 @@
-import { action, computed, IReactionDisposer, runInAction, reaction } from "mobx";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash } from '@fortawesome/free-solid-svg-icons';
+import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { Field, FieldWaiting, Opt } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import { DragManager } from "../../util/DragManager";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { Copy, ObjectField } from "../../../new_fields/ObjectField";
+import { Id } from "../../../new_fields/RefField";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { emptyFunction, Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+import { Docs } from "../../documents/Documents";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
+import { SearchUtil } from "../../util/SearchUtil";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
+import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
-import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import { VideoBox } from "../nodes/VideoBox";
-import { AudioBox } from "../nodes/AudioBox";
-import { Documents } from "../../documents/Documents"
-import { KeyValueBox } from "./KeyValueBox"
-import { WebBox } from "../nodes/WebBox";
-import { PDFBox } from "../nodes/PDFBox";
+import { DocComponent } from "../DocComponent";
+import { PresentationView } from "../PresentationView";
+import { Template } from "./../Templates";
+import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import React = require("react");
-import { TextField } from "../../../fields/TextField";
-import { DocumentManager } from "../../util/DocumentManager";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faTrash } from '@fortawesome/free-solid-svg-icons';
-import { faExpandArrowsAlt } from '@fortawesome/free-solid-svg-icons';
-import { faCompressArrowsAlt } from '@fortawesome/free-solid-svg-icons';
-import { faLayerGroup } from '@fortawesome/free-solid-svg-icons';
-import { faAlignCenter } from '@fortawesome/free-solid-svg-icons';
-import { faCaretSquareRight } from '@fortawesome/free-solid-svg-icons';
-import { faSquare } from '@fortawesome/free-solid-svg-icons';
library.add(faTrash);
library.add(faExpandArrowsAlt);
@@ -44,308 +40,333 @@ library.add(faAlignCenter);
library.add(faCaretSquareRight);
library.add(faSquare);
+const linkSchema = createSchema({
+ title: "string",
+ linkDescription: "string",
+ linkTags: "string",
+ linkedTo: Doc,
+ linkedFrom: Doc
+});
+
+type LinkDoc = makeInterface<[typeof linkSchema]>;
+const LinkDoc = makeInterface(linkSchema);
export interface DocumentViewProps {
- ContainingCollectionView: Opt<CollectionView>;
- Document: Document;
- AddDocument?: (doc: Document) => void;
- RemoveDocument?: (doc: Document) => boolean;
+ ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+ Document: Doc;
+ addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
+ removeDocument?: (doc: Document) => boolean;
+ moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
isTopMost: boolean;
ContentScaling: () => number;
PanelWidth: () => number;
PanelHeight: () => number;
focus: (doc: Document) => void;
- SelectOnLoad: boolean;
-}
-export interface JsxArgs extends DocumentViewProps {
- Keys: { [name: string]: Key }
- Fields: { [name: string]: Field }
+ selectOnLoad: boolean;
+ parentActive: () => boolean;
+ whenActiveChanged: (isActive: boolean) => void;
+ toggleMinimized: () => void;
+ bringToFront: (doc: Doc) => void;
+ addDocTab: (doc: Doc) => void;
}
-/*
-This function is pretty much a hack that lets us fill out the fields in JsxArgs with something that
-jsx-to-string can recover the jsx from
-Example usage of this function:
- public static LayoutString() {
- let args = FakeJsxArgs(["Data"]);
- return jsxToString(
- <CollectionFreeFormView
- doc={args.Document}
- fieldKey={args.Keys.Data}
- DocumentViewForField={args.DocumentView} />,
- { useFunctionCode: true, functionNameOnly: true }
- )
- }
-*/
-export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
- let Keys: { [name: string]: any } = {}
- let Fields: { [name: string]: any } = {}
- for (const key of keys) {
- let fn = () => { }
- Object.defineProperty(fn, "name", { value: key + "Key" })
- Keys[key] = fn;
- }
- for (const field of fields) {
- let fn = () => { }
- Object.defineProperty(fn, "name", { value: field })
- Fields[field] = fn;
- }
- let args: JsxArgs = {
- Document: function Document() { },
- DocumentView: function DocumentView() { },
- Keys,
- Fields
- } as any;
- return args;
-}
+const schema = createSchema({
+ layout: "string",
+ nativeWidth: "number",
+ nativeHeight: "number",
+ backgroundColor: "string"
+});
+
+export const positionSchema = createSchema({
+ nativeWidth: "number",
+ nativeHeight: "number",
+ width: "number",
+ height: "number",
+ x: "number",
+ y: "number",
+});
+
+export type PositionDocument = makeInterface<[typeof positionSchema]>;
+export const PositionDocument = makeInterface(positionSchema);
+
+type Document = makeInterface<[typeof schema]>;
+const Document = makeInterface(schema);
@observer
-export class DocumentView extends React.Component<DocumentViewProps> {
- private _mainCont = React.createRef<HTMLDivElement>();
- private _documentBindings: any = null;
+export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
private _downX: number = 0;
private _downY: number = 0;
- private _reactionDisposer: Opt<IReactionDisposer>;
- @computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); }
- @computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; }
- @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
- @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
- @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
- screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
- onPointerDown = (e: React.PointerEvent): void => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- if (e.shiftKey && e.buttons === 2) {
- if (this.props.isTopMost) {
- this.startDragging(e.pageX, e.pageY);
- }
- else CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e);
- e.stopPropagation();
- } else {
- if (this.active && !e.isDefaultPrevented()) {
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onPointerMove)
- document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp)
- document.addEventListener("pointerup", this.onPointerUp);
- }
- }
- }
-
- private dropDisposer?: DragManager.DragDropDisposer;
- protected createDropTarget = (ele: HTMLDivElement) => {
+ private _mainCont = React.createRef<HTMLDivElement>();
+ private _dropDisposer?: DragManager.DragDropDisposer;
+ public get ContentDiv() { return this._mainCont.current; }
+ @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
+ @computed get topMost(): boolean { return this.props.isTopMost; }
+ @computed get templates(): List<string> {
+ let field = this.props.Document.templates;
+ if (field && field instanceof List) {
+ return field;
+ }
+ return new List<string>();
}
+ set templates(templates: List<string>) { this.props.Document.templates = templates; }
+ screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
+ _reactionDisposer?: IReactionDisposer;
+ @action
componentDidMount() {
if (this._mainCont.current) {
- this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
+ handlers: { drop: this.drop.bind(this) }
+ });
}
- runInAction(() => {
- DocumentManager.Instance.DocumentViews.push(this);
- })
- this._reactionDisposer = reaction(
- () => this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.slice(),
+ // bcz: kind of ugly .. setup a reaction to update the title of a summary document's target (maximizedDocs) whenver the summary doc's title changes
+ this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
() => {
- if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.SelectedDocs.indexOf(this.props.Document.Id) != -1)
- SelectionManager.SelectDoc(this, true);
- });
+ let maxDoc = DocListCast(this.props.Document.maximizedDocs);
+ if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
+ this.props.Document.proto!.title = "-" + maxDoc[0].title + ".icon";
+ }
+ let sumDoc = Cast(this.props.Document.summaryDoc, Doc);
+ if (sumDoc instanceof Doc && StrCast(this.props.Document.title).startsWith("-")) {
+ this.props.Document.proto!.title = "-" + sumDoc.title + ".expanded";
+ }
+ }, { fireImmediately: true });
+ DocumentManager.Instance.DocumentViews.push(this);
}
-
+ @action
componentDidUpdate() {
- if (this.dropDisposer) {
- this.dropDisposer();
+ if (this._dropDisposer) {
+ this._dropDisposer();
}
if (this._mainCont.current) {
- this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } });
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
+ handlers: { drop: this.drop.bind(this) }
+ });
}
}
-
+ @action
componentWillUnmount() {
- if (this.dropDisposer) {
- this.dropDisposer();
- }
- runInAction(() => {
- DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+ if (this._reactionDisposer) this._reactionDisposer();
+ if (this._dropDisposer) this._dropDisposer();
+ DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+ }
- })
- if (this._reactionDisposer) {
- this._reactionDisposer();
- }
+ stopPropagation = (e: React.SyntheticEvent) => {
+ e.stopPropagation();
}
- startDragging(x: number, y: number) {
+ startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
if (this._mainCont.current) {
- const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- let dragData: { [id: string]: any } = {};
- dragData["documentView"] = this;
- dragData["xOffset"] = x - left;
- dragData["yOffset"] = y - top;
- DragManager.StartDrag(this._mainCont.current, dragData, {
+ let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
+ const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
+ let dragData = new DragManager.DocumentDragData(allConnected);
+ const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
+ dragData.dropAction = dropAction;
+ dragData.xOffset = xoff;
+ dragData.yOffset = yoff;
+ dragData.moveDocument = this.props.moveDocument;
+ DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
handlers: {
- dragComplete: action(() => { }),
+ dragComplete: action(emptyFunction)
},
- hideSource: true
- })
+ hideSource: !dropAction
+ });
}
}
- onPointerMove = (e: PointerEvent): void => {
- if (e.cancelBubble) {
+ onClick = (e: React.MouseEvent): void => {
+ if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
+ (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ }
+ }
+ _hitExpander = false;
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
return;
}
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- document.removeEventListener("pointermove", this.onPointerMove)
+ this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
+ if (e.shiftKey && e.buttons === 1) {
+ if (this.props.isTopMost) {
+ this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitExpander);
+ } else if (this.props.Document) {
+ CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
+ }
+ e.stopPropagation();
+ } else if (this.active) {
+ //e.stopPropagation(); // bcz: doing this will block click events from CollectionFreeFormDocumentView which are needed for iconifying,etc
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- if (!this.topMost || e.buttons == 2) {
- this.startDragging(e.x, e.y);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
+ onPointerMove = (e: PointerEvent): void => {
+ if (!e.cancelBubble) {
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
+ this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander);
+ }
}
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
}
- e.stopPropagation();
- e.preventDefault();
}
onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove)
- document.removeEventListener("pointerup", this.onPointerUp)
- e.stopPropagation();
- if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
}
deleteClicked = (): void => {
- if (this.props.RemoveDocument) {
- this.props.RemoveDocument(this.props.Document);
- }
+ this.props.removeDocument && this.props.removeDocument(this.props.Document);
}
-
fieldsClicked = (e: React.MouseEvent): void => {
- if (this.props.AddDocument) {
- this.props.AddDocument(Documents.KVPDocument(this.props.Document));
+ let kvp = Docs.KVPDocument(this.props.Document, { title: this.props.Document.title + ".kvp", width: 300, height: 300 });
+ CollectionDockingView.Instance.AddRightSplit(kvp);
+ }
+ makeButton = (e: React.MouseEvent): void => {
+ let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ doc.isButton = !BoolCast(doc.isButton, false);
+ if (doc.isButton && !doc.nativeWidth) {
+ doc.nativeWidth = this.props.Document[WidthSym]();
+ doc.nativeHeight = this.props.Document[HeightSym]();
+ } else {
+
+ doc.nativeWidth = doc.nativeHeight = undefined;
}
}
fullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
+ const doc = Doc.MakeCopy(this.props.Document, false);
+ if (doc) {
+ CollectionDockingView.Instance.OpenFullScreen(doc);
+ }
ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked, icon: "compress-arrows-alt" });
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ SelectionManager.DeselectAll();
}
- closeFullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.Instance.CloseFullScreen();
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ @undoBatch
+ @action
+ drop = async (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.LinkDragData) {
+ let sourceDoc = de.data.linkSourceDocument;
+ let destDoc = this.props.Document;
+
+ if (de.mods === "AltKey") {
+ const protoDest = destDoc.proto;
+ const protoSrc = sourceDoc.proto;
+ let src = protoSrc ? protoSrc : sourceDoc;
+ let dst = protoDest ? protoDest : destDoc;
+ dst.data = (src.data! as ObjectField)[Copy]();
+ dst.nativeWidth = src.nativeWidth;
+ dst.nativeHeight = src.nativeHeight;
+ }
+ else {
+ Doc.MakeLink(sourceDoc, destDoc);
+ de.data.droppedDocuments.push(destDoc);
+ }
+ e.stopPropagation();
+ }
}
@action
- drop = (e: Event, de: DragManager.DropEvent) => {
- console.log("drop");
- const sourceDocView: DocumentView = de.data["linkSourceDoc"];
- if (!sourceDocView) {
- return;
- }
- let sourceDoc: Document = sourceDocView.props.Document;
- let destDoc: Document = this.props.Document;
- if (this.props.isTopMost) {
- return;
+ onDrop = (e: React.DragEvent) => {
+ let text = e.dataTransfer.getData("text/plain");
+ if (!e.isDefaultPrevented() && text && text.startsWith("<div")) {
+ let oldLayout = FieldValue(this.Document.layout) || "";
+ let layout = text.replace("{layout}", oldLayout);
+ this.Document.layout = layout;
+ e.stopPropagation();
+ e.preventDefault();
}
- let linkDoc: Document = new Document();
-
- linkDoc.Set(KeyStore.Title, new TextField("New Link"));
- linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
- linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
-
- sourceDoc.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
- linkDoc.Set(KeyStore.LinkedToDocs, destDoc);
- destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
- linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc);
-
+ }
+ @action
+ addTemplate = (template: Template) => {
+ this.templates.push(template.Layout);
+ this.templates = this.templates;
+ }
- e.stopPropagation();
+ @action
+ removeTemplate = (template: Template) => {
+ for (let i = 0; i < this.templates.length; i++) {
+ if (this.templates[i] === template.Layout) {
+ this.templates.splice(i, 1);
+ break;
+ }
+ }
+ this.templates = this.templates;
}
@action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
- let moved = Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3;
- if (moved || e.isDefaultPrevented()) {
- e.preventDefault()
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
+ e.isDefaultPrevented()) {
+ e.preventDefault();
return;
}
- e.preventDefault()
+ e.preventDefault();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" })
- ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" })
- ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document), icon: "align-center" })
- ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" })
- ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking), icon: "square" })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ const cm = ContextMenu.Instance;
+ cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" });
+ cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
+ cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document), icon: "align-center" });
+ cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document), icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
+ cm.addItem({
+ description: "Find aliases", event: async () => {
+ const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+ CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {}));
+ }, icon: "expand-arrows-alt"
+ });
+ cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
if (!this.topMost) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
}
-
- ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
-
- get mainContent() {
- return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, VideoBox, AudioBox, PDFBox }}
- bindings={this._documentBindings}
- jsx={this.layout}
- showWarnings={true}
- onError={(test: any) => { console.log(test) }}
- />
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ if (!SelectionManager.IsSelected(this)) {
+ SelectionManager.SelectDoc(this, false);
+ }
}
- isSelected = () => {
- return SelectionManager.IsSelected(this);
- }
+ isSelected = () => SelectionManager.IsSelected(this);
+ select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
- select = (ctrlPressed: boolean) => {
- SelectionManager.SelectDoc(this, ctrlPressed)
- }
+ @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
+ @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+ @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={"layout"} />); }
render() {
- if (!this.props.Document) return <div></div>
- let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
- if (!lkeys || lkeys === "<Waiting>") {
- return <p>Error loading layout keys</p>;
- }
- this._documentBindings = {
- ...this.props,
- isSelected: this.isSelected,
- select: this.select,
- focus: this.props.focus
- };
- for (const key of this.layoutKeys) {
- this._documentBindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
- }
- for (const key of this.layoutFields) {
- let field = this.props.Document.Get(key);
- this._documentBindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
- }
- this._documentBindings.bindings = this._documentBindings;
var scaling = this.props.ContentScaling();
- var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+ var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : (StrCast(this.props.Document.layout).indexOf("IconBox") === -1 ? "100%" : "auto");
+ var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
+
return (
- <div className="documentView-node" ref={this._mainCont}
+ <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
+ ref={this._mainCont}
style={{
- width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
- height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
- transformOrigin: "left top",
- transform: `scale(${scaling} , ${scaling})`
+ borderRadius: "inherit",
+ background: this.Document.backgroundColor || "",
+ width: nativeWidth,
+ height: nativeHeight,
+ transform: `scale(${scaling}, ${scaling})`
}}
- onContextMenu={this.onContextMenu}
- onPointerDown={this.onPointerDown} >
- {this.mainContent}
+ onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
+ >
+ {this.contents}
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/FieldTextBox.scss b/src/client/views/nodes/FieldTextBox.scss
index b6ce2fabc..d2cd61b0d 100644
--- a/src/client/views/nodes/FieldTextBox.scss
+++ b/src/client/views/nodes/FieldTextBox.scss
@@ -1,14 +1,14 @@
.ProseMirror {
- margin-top: -1em;
- width: 100%;
- height: 100%;
+ margin-top: -1em;
+ width: 100%;
+ height: 100%;
}
.ProseMirror:focus {
- outline: none !important
+ outline: none !important;
}
.fieldTextBox-cont {
- background: white;
- padding: 1vw;
-} \ No newline at end of file
+ background: white;
+ padding: 1vw;
+}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 49f4cefce..b5dfd18eb 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,21 +1,24 @@
-import React = require("react")
+import React = require("react");
import { observer } from "mobx-react";
-import { computed } from "mobx";
-import { Field, FieldWaiting, FieldValue } from "../../../fields/Field";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
-import { NumberField } from "../../../fields/NumberField";
-import { RichTextField } from "../../../fields/RichTextField";
-import { ImageField } from "../../../fields/ImageField";
-import { WebField } from "../../../fields/WebField";
-import { VideoField } from "../../../fields/VideoField"
-import { Key } from "../../../fields/Key";
+import { computed, observable } from "mobx";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
-import { WebBox } from "./WebBox";
import { VideoBox } from "./VideoBox";
import { AudioBox } from "./AudioBox";
-import { AudioField } from "../../../fields/AudioField";
+import { DocumentContentsView } from "./DocumentContentsView";
+import { Transform } from "../../util/Transform";
+import { returnFalse, emptyFunction, returnOne } from "../../../Utils";
+import { CollectionView } from "../collections/CollectionView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { IconBox } from "./IconBox";
+import { Opt, Doc, FieldResult } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField";
+import { IconField } from "../../../new_fields/IconField";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { DateField } from "../../../new_fields/DateField";
+import { NumCast } from "../../../new_fields/Types";
//
@@ -24,61 +27,101 @@ import { AudioField } from "../../../fields/AudioField";
// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc.
//
export interface FieldViewProps {
- fieldKey: Key;
- doc: Document;
+ fieldKey: string;
+ ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+ Document: Doc;
isSelected: () => boolean;
- select: () => void;
+ select: (isCtrlPressed: boolean) => void;
isTopMost: boolean;
selectOnLoad: boolean;
- bindings: any;
+ addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
+ addDocTab: (document: Doc) => boolean;
+ removeDocument?: (document: Doc) => boolean;
+ moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ ScreenToLocalTransform: () => Transform;
+ active: () => boolean;
+ whenActiveChanged: (isActive: boolean) => void;
+ focus: (doc: Doc) => void;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ setVideoBox?: (player: VideoBox) => void;
}
@observer
export class FieldView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") {
- return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost} />`;
+ public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") {
+ return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} />`;
}
@computed
- get field(): FieldValue<Field> {
- const { doc, fieldKey } = this.props;
- return doc.Get(fieldKey);
+ get field(): FieldResult {
+ const { Document, fieldKey } = this.props;
+ return Document[fieldKey];
}
render() {
const field = this.field;
- if (!field) {
- return <p>{'<null>'}</p>
- }
- if (field instanceof TextField) {
- return <p>{field.Data}</p>
+ if (field === undefined) {
+ return <p>{'<null>'}</p>;
}
+ // if (typeof field === "string") {
+ // return <p>{field}</p>;
+ // }
else if (field instanceof RichTextField) {
- return <FormattedTextBox {...this.props} />
+ return <FormattedTextBox {...this.props} />;
}
else if (field instanceof ImageField) {
- return <ImageBox {...this.props} />
+ return <ImageBox {...this.props} />;
+ }
+ else if (field instanceof IconField) {
+ return <IconBox {...this.props} />;
+ }
+ else if (field instanceof VideoField) {
+ return <VideoBox {...this.props} />;
+ }
+ else if (field instanceof AudioField) {
+ return <AudioBox {...this.props} />;
+ } else if (field instanceof DateField) {
+ return <p>{field.date.toLocaleString()}</p>;
}
- else if (field instanceof WebField) {
- return <WebBox {...this.props} />
- }
- else if (field instanceof VideoField){
- return <VideoBox {...this.props}/>
+ else if (field instanceof Doc) {
+ let returnHundred = () => 100;
+ return (
+ <DocumentContentsView Document={field}
+ addDocument={undefined}
+ addDocTab={this.props.addDocTab}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={returnHundred}
+ PanelHeight={returnHundred}
+ isTopMost={true} //TODO Why is this top most?
+ selectOnLoad={false}
+ focus={emptyFunction}
+ isSelected={this.props.isSelected}
+ select={returnFalse}
+ layoutKey={"layout"}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ parentActive={this.props.active}
+ toggleMinimized={emptyFunction}
+ whenActiveChanged={this.props.whenActiveChanged}
+ bringToFront={emptyFunction} />
+ );
}
- else if (field instanceof AudioField){
- return <AudioBox {...this.props}/>
+ else if (field instanceof List) {
+ return (<div>
+ {field.map(f => f instanceof Doc ? f.title : f.toString()).join(", ")}
+ </div>);
}
// bcz: this belongs here, but it doesn't render well so taking it out for now
// else if (field instanceof HtmlField) {
// return <WebBox {...this.props} />
// }
- else if (field instanceof NumberField) {
- return <p>{field.Data}</p>
+ else if (!(field instanceof Promise)) {
+ return <p>{JSON.stringify(field)}</p>;
}
- else if (field != FieldWaiting) {
- return <p>{JSON.stringify(field.GetValue())}</p>
+ else {
+ return <p> {"Waiting for server..."} </p>;
}
- else
- return <p> {"Waiting for server..."} </p>
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index ab5849f09..4a29c1949 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -1,38 +1,60 @@
+@import "../globalCssVariables";
.ProseMirror {
- width: 100%;
- height: auto;
- min-height: 100%
+ width: 100%;
+ height: auto;
+ min-height: 100%;
+ font-family: $serif;
}
.ProseMirror:focus {
- outline: none !important
+ outline: none !important;
}
-.formattedTextBox-cont {
- background: white;
- padding: 1;
- border-width: 1px;
- border-radius: 2px;
- border-color:black;
- box-sizing: border-box;
- background: white;
- border-style:solid;
- overflow-y: scroll;
- overflow-x: hidden;
- color: initial;
- height: 100%;
+.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
+ background: inherit;
+ padding: 0;
+ border-width: 0px;
+ border-radius: inherit;
+ border-color: $intermediate-color;
+ box-sizing: border-box;
+ background-color: inherit;
+ border-style: solid;
+ overflow-y: auto;
+ overflow-x: hidden;
+ color: initial;
+ height: 100%;
+ pointer-events: all;
+}
+
+.formattedTextBox-cont-hidden {
+ pointer-events: none;
+}
+.formattedTextBox-inner-rounded {
+ height: calc(100% - 25px);
+ width: calc(100% - 40px);
+ position: absolute;
+ overflow: auto;
+ top: 15;
+ left: 20;
}
.menuicon {
- display: inline-block;
- border-right: 1px solid rgba(0, 0, 0, 0.2);
- color: #888;
- line-height: 1;
- padding: 0 7px;
- margin: 1px;
- cursor: pointer;
- text-align: center;
- min-width: 1.4em;
- }
- .strong, .heading { font-weight: bold; }
- .em { font-style: italic; } \ No newline at end of file
+ display: inline-block;
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+ color: #888;
+ line-height: 1;
+ padding: 0 7px;
+ margin: 1px;
+ cursor: pointer;
+ text-align: center;
+ min-width: 1.4em;
+}
+
+.strong,
+.heading {
+ font-weight: bold;
+}
+
+.em {
+ font-style: italic;
+}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 648037e31..98abde89e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,30 +1,41 @@
-import { action, IReactionDisposer, reaction } from "mobx";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit, faSmile } from '@fortawesome/free-solid-svg-icons';
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
-import { history, redo, undo } from "prosemirror-history";
+import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
-import { schema } from "../../util/RichTextSchema";
-import { EditorState, Transaction, } from "prosemirror-state";
+import { EditorState, Plugin, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { Opt, FieldWaiting } from "../../../fields/Field";
-import "./FormattedTextBox.scss";
-import React = require("react")
-import { RichTextField } from "../../../fields/RichTextField";
-import { FieldViewProps, FieldView } from "./FieldView";
-import { Plugin } from 'prosemirror-state'
-import { Decoration, DecorationSet } from 'prosemirror-view'
-import { TooltipTextMenu } from "../../util/TooltipTextMenu"
+import { Doc, Field, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { DocServer } from "../../DocServer";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DragManager } from "../../util/DragManager";
+import buildKeymap from "../../util/ProsemirrorKeymap";
+import { inpRules } from "../../util/RichTextRules";
+import { ImageResizeView, schema } from "../../util/RichTextSchema";
+import { SelectionManager } from "../../util/SelectionManager";
+import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
+import { TooltipTextMenu } from "../../util/TooltipTextMenu";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import { ContextMenu } from "../../views/ContextMenu";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit } from '@fortawesome/free-solid-svg-icons';
-import { faSmile } from '@fortawesome/free-solid-svg-icons';
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { DocComponent } from "../DocComponent";
+import { InkingControl } from "../InkingControl";
+import { FieldView, FieldViewProps } from "./FieldView";
+import "./FormattedTextBox.scss";
+import React = require("react");
library.add(faEdit);
library.add(faSmile);
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
-// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
-//
+// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name}
+//
// In Code, the node's HTML is specified in the document's parameterized structure as:
// document.SetField(KeyStore.Layout, "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
// and the node's binding to the specified document KEYNAME as:
@@ -33,67 +44,161 @@ library.add(faSmile);
// 'fieldKey' property to the Key stored in LayoutKeys
// and 'doc' property to the document that is being rendered
//
-// When rendered() by React, this extracts the TextController from the Document stored at the
-// specified Key and assigns it to an HTML input node. When changes are made tot his node,
+// When rendered() by React, this extracts the TextController from the Document stored at the
+// specified Key and assigns it to an HTML input node. When changes are made to this node,
// this will edit the document and assign the new value to that field.
//]
-export class FormattedTextBox extends React.Component<FieldViewProps> {
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(FormattedTextBox, fieldStr) }
+export interface FormattedTextBoxOverlay {
+ isOverlay?: boolean;
+}
+
+const richTextSchema = createSchema({
+ documentText: "string"
+});
+
+type RichTextDocument = makeInterface<[typeof richTextSchema]>;
+const RichTextDocument = makeInterface(richTextSchema);
+
+@observer
+export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) {
+ public static LayoutString(fieldStr: string = "data") {
+ return FieldView.LayoutString(FormattedTextBox, fieldStr);
+ }
private _ref: React.RefObject<HTMLDivElement>;
+ private _proseRef: React.RefObject<HTMLDivElement>;
private _editorView: Opt<EditorView>;
+ private _gotDown: boolean = false;
+ private _dropDisposer?: DragManager.DragDropDisposer;
private _reactionDisposer: Opt<IReactionDisposer>;
+ private _inputReactionDisposer: Opt<IReactionDisposer>;
+ private _proxyReactionDisposer: Opt<IReactionDisposer>;
+ public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+
+ @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
+ public static InputBoxOverlayScroll: number = 0;
constructor(props: FieldViewProps) {
super(props);
this._ref = React.createRef();
- this.onChange = this.onChange.bind(this);
+ this._proseRef = React.createRef();
+ if (this.props.isOverlay) {
+ DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
+ }
}
+ _applyingChange: boolean = false;
+
+ _lastState: any = undefined;
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
- const state = this._editorView.state.apply(tx);
+ const state = this._lastState = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- this.props.doc.SetData(this.props.fieldKey, JSON.stringify(state.toJSON()), RichTextField);
+ this._applyingChange = true;
+ Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
+ Doc.SetOnPrototype(this.props.Document, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+ this._applyingChange = false;
+ let title = StrCast(this.props.Document.title);
+ if (title && title.startsWith("-") && this._editorView) {
+ let str = this._editorView.state.doc.textContent;
+ let titlestr = str.substr(0, Math.min(40, str.length));
+ let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+ }
+ }
+ }
+
+ @undoBatch
+ @action
+ drop = async (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.LinkDragData) {
+ let sourceDoc = de.data.linkSourceDocument;
+ let destDoc = this.props.Document;
+
+ Doc.MakeLink(sourceDoc, destDoc);
+ de.data.droppedDocuments.push(destDoc);
+ e.stopPropagation();
}
}
componentDidMount() {
- let state: EditorState;
+ if (this._ref.current) {
+ this._dropDisposer = DragManager.MakeDropTarget(this._ref.current, {
+ handlers: { drop: this.drop.bind(this) }
+ });
+ }
const config = {
schema,
- plugins: [
+ inpRules, //these currently don't do anything, but could eventually be helpful
+ plugins: this.props.isOverlay ? [
+ this.tooltipTextMenuPlugin(),
history(),
- keymap({ "Mod-z": undo, "Mod-y": redo }),
+ keymap(buildKeymap(schema)),
keymap(baseKeymap),
- this.tooltipMenuPlugin()
- ]
+ // this.tooltipLinkingMenuPlugin(),
+ new Plugin({
+ props: {
+ attributes: { class: "ProseMirror-example-setup-style" }
+ }
+ })
+ ] : [
+ history(),
+ keymap(buildKeymap(schema)),
+ keymap(baseKeymap),
+ ]
};
- let field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
- if (field && field != FieldWaiting && field.Data) {
- state = EditorState.fromJSON(config, JSON.parse(field.Data));
+ if (this.props.isOverlay) {
+ this._inputReactionDisposer = reaction(() => FormattedTextBox.InputBoxOverlay,
+ () => {
+ if (this._editorView) {
+ this._editorView.destroy();
+ }
+ this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
+ }
+ );
} else {
- state = EditorState.create(config);
+ this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
+ () => {
+ if (this.props.isSelected()) {
+ FormattedTextBox.InputBoxOverlay = this;
+ FormattedTextBox.InputBoxOverlayScroll = this._ref.current!.scrollTop;
+ }
+ });
}
- if (this._ref.current) {
- this._editorView = new EditorView(this._ref.current, {
- state,
- dispatchTransaction: this.dispatchTransaction
+
+
+ this._reactionDisposer = reaction(
+ () => {
+ const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined;
+ return field ? field.Data : undefined;
+ },
+ field => field && this._editorView && !this._applyingChange &&
+ this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
+ );
+ this.setupEditor(config, this.props.Document);
+ }
+
+ private setupEditor(config: any, doc?: Doc) {
+ let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined;
+ if (this._proseRef.current) {
+ this._editorView = new EditorView(this._proseRef.current, {
+ state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
+ dispatchTransaction: this.dispatchTransaction,
+ nodeViews: {
+ image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }
+ }
});
+ let text = StrCast(this.props.Document.documentText);
+ if (text.startsWith("@@@")) {
+ this.props.Document.proto!.documentText = undefined;
+ this._editorView.dispatch(this._editorView.state.tr.insertText(text.replace("@@@", "")));
+ }
}
- this._reactionDisposer = reaction(() => {
- const field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
- return field && field != FieldWaiting ? field.Data : undefined;
- }, (field) => {
- if (field && this._editorView) {
- this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
- }
- })
if (this.props.selectOnLoad) {
- this.props.select();
+ this.props.select(false);
this._editorView!.focus();
}
}
@@ -105,60 +210,175 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
if (this._reactionDisposer) {
this._reactionDisposer();
}
+ if (this._inputReactionDisposer) {
+ this._inputReactionDisposer();
+ }
+ if (this._proxyReactionDisposer) {
+ this._proxyReactionDisposer();
+ }
+ if (this._dropDisposer) {
+ this._dropDisposer();
+ }
}
- shouldComponentUpdate() {
- return false;
- }
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ e.stopPropagation();
+ if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
+ this._toolTipTextMenu.tooltip.style.opacity = "0";
+ }
+ }
+ if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey) {
+ if (e.target && (e.target as any).href) {
+ let href = (e.target as any).href;
+ if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
+ let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
+ DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+ if (f instanceof Doc) {
+ if (DocumentManager.Instance.getDocumentView(f)) {
+ DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(f);
+ }
+ }
+ }));
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
- @action
- onChange(e: React.ChangeEvent<HTMLInputElement>) {
- this.props.doc.SetData(this.props.fieldKey, e.target.value, RichTextField);
+ }
+ if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
+ this._gotDown = true;
+ e.preventDefault();
+ }
}
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected()) {
+ onPointerUp = (e: React.PointerEvent): void => {
+ if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
+ this._toolTipTextMenu.tooltip.style.opacity = "1";
+ }
+ if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
}
- //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
- textCapability = (e: React.MouseEvent): void => {
+ @action
+ onFocused = (e: React.FocusEvent): void => {
+ if (!this.props.isOverlay) {
+ FormattedTextBox.InputBoxOverlay = this;
+ } else {
+ if (this._ref.current) {
+ this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
+ }
+ }
}
- @action
+ //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
+ freezeNativeDimensions = (e: React.MouseEvent): void => {
+ if (NumCast(this.props.Document.nativeWidth)) {
+ this.props.Document.proto!.nativeWidth = undefined;
+ this.props.Document.proto!.nativeHeight = undefined;
+
+ } else {
+ this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
+ this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
+ }
+ }
specificContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability, icon: "edit" });
+ if (!this._gotDown) {
+ e.preventDefault();
+ return;
+ }
ContextMenu.Instance.addItem({
- description: "Sub", subitems: [
- { description: "Subitem 1", event: this.textCapability, icon: "smile" },
- { description: "Subitem 2", event: this.textCapability, icon: "smile" },
- { description: "Subitem 3", event: this.textCapability, icon: "smile" },
- { description: "Submenu", subitems: [{ description: "Inner Subitem", event: this.textCapability, icon: "smile" }] }
- ]
+ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze",
+ event: this.freezeNativeDimensions,
+ icon: "edit"
});
}
onPointerWheel = (e: React.WheelEvent): void => {
- e.stopPropagation();
+ if (this.props.isSelected()) {
+ e.stopPropagation();
+ }
}
- tooltipMenuPlugin() {
+ onClick = (e: React.MouseEvent): void => {
+ this._proseRef.current!.focus();
+ }
+ onMouseDown = (e: React.MouseEvent): void => {
+ if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself
+ e.preventDefault(); // bcz: this would normally be in OnPointerDown - however, if done there, no mouse move events will be generated which makes transititioning to GoldenLayout's drag interactions impossible
+ }
+ }
+
+ tooltipTextMenuPlugin() {
+ let myprops = this.props;
+ let self = this;
return new Plugin({
view(_editorView) {
- return new TooltipTextMenu(_editorView)
+ return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
}
- })
+ });
}
- onKeyPress(e: React.KeyboardEvent) {
+ _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
+ tooltipLinkingMenuPlugin() {
+ let myprops = this.props;
+ return new Plugin({
+ view(_editorView) {
+ return new TooltipLinkingMenu(_editorView, myprops);
+ }
+ });
+ }
+ onBlur = (e: any) => {
+ if (this._undoTyping) {
+ this._undoTyping.end();
+ this._undoTyping = undefined;
+ }
+ }
+ public _undoTyping?: UndoManager.Batch;
+ onKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === "Escape") {
+ SelectionManager.DeselectAll();
+ }
e.stopPropagation();
+ if (e.key === "Tab") e.preventDefault();
+ // stop propagation doesn't seem to stop propagation of native keyboard events.
+ // so we set a flag on the native event that marks that the event's been handled.
+ (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
+ if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) {
+ let str = this._editorView.state.doc.textContent;
+ let titlestr = str.substr(0, Math.min(40, str.length));
+ let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+ }
+ if (!this._undoTyping) {
+ this._undoTyping = UndoManager.StartBatch("undoTyping");
+ }
}
render() {
- return (<div className="formattedTextBox-cont"
- onKeyPress={this.onKeyPress}
- onPointerDown={this.onPointerDown}
- onContextMenu={this.specificContextMenu}
- onWheel={this.onPointerWheel}
- ref={this._ref} />)
- }
-} \ No newline at end of file
+ let style = this.props.isOverlay ? "scroll" : "hidden";
+ let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : "";
+ let interactive = InkingControl.Instance.selectedTool ? "" : "interactive";
+ return (
+ <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
+ style={{
+ pointerEvents: interactive ? "all" : "none",
+ }}
+ onKeyDown={this.onKeyPress}
+ onKeyPress={this.onKeyPress}
+ onFocus={this.onFocused}
+ onClick={this.onClick}
+ onBlur={this.onBlur}
+ onPointerUp={this.onPointerUp}
+ onPointerDown={this.onPointerDown}
+ onMouseDown={this.onMouseDown}
+ onContextMenu={this.specificContextMenu}
+ // tfs: do we need this event handler
+ onWheel={this.onPointerWheel}
+ >
+ <div className={`formattedTextBox-inner${rounded}`} style={{ pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
new file mode 100644
index 000000000..893dc2d36
--- /dev/null
+++ b/src/client/views/nodes/IconBox.scss
@@ -0,0 +1,22 @@
+
+@import "../globalCssVariables";
+.iconBox-container {
+ position: inherit;
+ left:0;
+ top:0;
+ height: auto;
+ width: max-content;
+ // overflow: hidden;
+ pointer-events: all;
+ svg {
+ width: $MINIMIZED_ICON_SIZE !important;
+ height: auto;
+ background: white;
+ }
+ .iconBox-label {
+ position: absolute;
+ width:max-content;
+ font-size: 14px;
+ margin-top: 3px;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
new file mode 100644
index 000000000..b42eb44a5
--- /dev/null
+++ b/src/client/views/nodes/IconBox.tsx
@@ -0,0 +1,84 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./IconBox.scss";
+import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { IconField } from "../../../new_fields/IconField";
+import { ContextMenu } from "../ContextMenu";
+import Measure from "react-measure";
+import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
+import { listSpec } from "../../../new_fields/Schema";
+
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+@observer
+export class IconBox extends React.Component<FieldViewProps> {
+ public static LayoutString() { return FieldView.LayoutString(IconBox); }
+
+ @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
+ @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
+
+ public static DocumentIcon(layout: string) {
+ let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+ layout.indexOf("ImageBox") !== -1 ? faImage :
+ layout.indexOf("Formatted") !== -1 ? faStickyNote :
+ layout.indexOf("Video") !== -1 ? faFilm :
+ layout.indexOf("Collection") !== -1 ? faObjectGroup :
+ faCaretUp;
+ return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
+ }
+
+ setLabelField = (e: React.MouseEvent): void => {
+ this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel);
+ }
+ setUseOwnTitleField = (e: React.MouseEvent): void => {
+ this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle);
+ }
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ ContextMenu.Instance.addItem({
+ description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon",
+ event: this.setLabelField
+ });
+ let maxDocs = DocListCast(this.props.Document.maximizedDocs);
+ if (maxDocs.length === 1 && !BoolCast(this.props.Document.hideLabel)) {
+ ContextMenu.Instance.addItem({
+ description: BoolCast(this.props.Document.useOwnTitle) ? "Use target title for label" : "Use own title label",
+ event: this.setUseOwnTitleField
+ });
+ }
+ }
+ @observable _panelWidth: number = 0;
+ @observable _panelHeight: number = 0;
+ render() {
+ let labelField = StrCast(this.props.Document.labelField);
+ let hideLabel = BoolCast(this.props.Document.hideLabel);
+ let maxDocs = DocListCast(this.props.Document.maximizedDocs);
+ let firstDoc = maxDocs.length ? maxDocs[0] : undefined;
+ let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle, false) ? firstDoc[labelField] : this.props.Document.title);
+ return (
+ <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
+ {this.minimizedIcon}
+ <Measure offset onResize={(r) => runInAction(() => {
+ if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) {
+ this.props.Document.nativeWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE));
+ if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.nativeWidth;
+ }
+ })}>
+ {({ measureRef }) =>
+ <span ref={measureRef} className="iconBox-label">{label}</span>
+ }
+ </Measure>
+ </div>);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index ea459b911..f1b73a676 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,22 +1,41 @@
-
.imageBox-cont {
- padding: 0vw;
- position: relative;
- text-align: center;
- width: 100%;
- height: auto;
- max-width: 100%;
- max-height: 100%
+ padding: 0vw;
+ position: relative;
+ text-align: center;
+ width: 100%;
+ height: auto;
+ max-width: 100%;
+ max-height: 100%;
+ pointer-events: none;
+}
+.imageBox-cont-interactive {
+ pointer-events: all;
+ width:100%;
+ height:auto;
+}
+
+.imageBox-dot {
+ position:absolute;
+ bottom: 10;
+ left: 0;
+ border-radius: 10px;
+ width:20px;
+ height:20px;
+ background:gray;
}
.imageBox-cont img {
- object-fit: contain;
- height: 100%;
+ height: auto;
+ width:100%;
+}
+.imageBox-cont-interactive img {
+ height: auto;
+ width:100%;
}
.imageBox-button {
- padding : 0vw;
- border: none;
- width : 100%;
- height: 100%;
+ padding: 0vw;
+ border: none;
+ width: 100%;
+ height: 100%;
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 7532e23c8..828ac9bc8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -2,54 +2,93 @@ import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { FieldWaiting } from '../../../fields/Field';
-import { ImageField } from '../../../fields/ImageField';
-import { KeyStore } from '../../../fields/KeyStore';
+import { Utils } from '../../../Utils';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../../views/ContextMenu";
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
-import React = require("react")
+import React = require("react");
+import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
+import { DocComponent } from '../DocComponent';
+import { positionSchema } from './DocumentView';
+import { FieldValue, Cast, StrCast } from '../../../new_fields/Types';
+import { ImageField } from '../../../new_fields/URLField';
+import { List } from '../../../new_fields/List';
+import { InkingControl } from '../InkingControl';
+import { Doc } from '../../../new_fields/Doc';
+
+export const pageSchema = createSchema({
+ curPage: "number"
+});
+
+type ImageDocument = makeInterface<[typeof pageSchema, typeof positionSchema]>;
+const ImageDocument = makeInterface(pageSchema, positionSchema);
@observer
-export class ImageBox extends React.Component<FieldViewProps> {
+export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
- public static LayoutString() { return FieldView.LayoutString(ImageBox) }
- private _ref: React.RefObject<HTMLDivElement>;
- private _imgRef: React.RefObject<HTMLImageElement>;
+ public static LayoutString() { return FieldView.LayoutString(ImageBox); }
+ private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _downX: number = 0;
private _downY: number = 0;
private _lastTap: number = 0;
@observable private _photoIndex: number = 0;
@observable private _isOpen: boolean = false;
-
- constructor(props: FieldViewProps) {
- super(props);
-
- this._ref = React.createRef();
- this._imgRef = React.createRef();
- this.state = {
- photoIndex: 0,
- isOpen: false,
- };
- }
+ private dropDisposer?: DragManager.DragDropDisposer;
@action
onLoad = (target: any) => {
var h = this._imgRef.current!.naturalHeight;
var w = this._imgRef.current!.naturalWidth;
- this.props.doc.SetNumber(KeyStore.NativeHeight, this.props.doc.GetNumber(KeyStore.NativeWidth, 0) * h / w)
+ if (this._photoIndex === 0 && (this.props as any).id !== "isExpander") {
+ Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w);
+ this.Document.height = FieldValue(this.Document.width, 0) * h / w;
+ }
}
- componentDidMount() {
+
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
}
+ onDrop = (e: React.DragEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ console.log("IMPLEMENT ME PLEASE");
+ }
+
- componentWillUnmount() {
+ @undoBatch
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ de.data.droppedDocuments.forEach(action((drop: Doc) => {
+ let layout = StrCast(drop.backgroundLayout);
+ if (layout.indexOf(ImageBox.name) !== -1) {
+ let imgData = this.props.Document[this.props.fieldKey];
+ if (imgData instanceof ImageField) {
+ Doc.SetOnPrototype(this.props.Document, "data", new List([imgData]));
+ }
+ let imgList = Cast(this.props.Document[this.props.fieldKey], listSpec(ImageField), [] as any[]);
+ if (imgList) {
+ let field = drop.data;
+ if (field instanceof ImageField) imgList.push(field);
+ else if (field instanceof List) imgList.concat(field);
+ }
+ e.stopPropagation();
+ }
+ }));
+ // de.data.removeDocument() bcz: need to implement
+ }
}
onPointerDown = (e: React.PointerEvent): void => {
if (Date.now() - this._lastTap < 300) {
- if (e.buttons === 1 && this.props.isSelected()) {
- e.stopPropagation();
+ if (e.buttons === 1) {
this._downX = e.clientX;
this._downY = e.clientY;
document.removeEventListener("pointerup", this.onPointerUp);
@@ -68,9 +107,8 @@ export class ImageBox extends React.Component<FieldViewProps> {
e.stopPropagation();
}
- lightbox = (path: string) => {
- const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"];
- if (this._isOpen && this.props.isSelected()) {
+ lightbox = (images: string[]) => {
+ if (this._isOpen) {
return (<Lightbox
mainSrc={images[this._photoIndex]}
nextSrc={images[(this._photoIndex + 1) % images.length]}
@@ -84,27 +122,58 @@ export class ImageBox extends React.Component<FieldViewProps> {
onMoveNextRequest={action(() =>
this._photoIndex = (this._photoIndex + 1) % images.length
)}
- />)
+ />);
}
}
- //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
- imageCapability = (e: React.MouseEvent): void => {
+ specificContextMenu = (e: React.MouseEvent): void => {
+ let field = Cast(this.Document[this.props.fieldKey], ImageField);
+ if (field) {
+ let url = field.url.href;
+ ContextMenu.Instance.addItem({
+ description: "Copy path", event: () => {
+ Utils.CopyText(url);
+ }, icon: "expand-arrows-alt"
+ });
+ }
}
- specificContextMenu = (e: React.MouseEvent): void => {
- ContextMenu.Instance.addItem({ description: "Image Capability", event: this.imageCapability, icon: "smile" });
+ @action
+ onDotDown(index: number) {
+ this._photoIndex = index;
+ this.Document.curPage = index;
+ }
+
+ dots(paths: string[]) {
+ let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
+ let dist = Math.min(nativeWidth / paths.length, 40);
+ let left = (nativeWidth - paths.length * dist) / 2;
+ return paths.map((p, i) =>
+ <div className="imageBox-placer" key={i} >
+ <div className="imageBox-dot" style={{ background: (i === this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
+ </div>
+ );
}
render() {
- let field = this.props.doc.Get(this.props.fieldKey);
- let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
- field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
- let nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1);
+ let field = this.Document[this.props.fieldKey];
+ let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
+ if (field instanceof ImageField) paths = [field.url.href];
+ else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href);
+ let nativeWidth = FieldValue(this.Document.nativeWidth, (this.props.PanelWidth as any) as string ? Number((this.props.PanelWidth as any) as string) : 50);
+ let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
+ let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx
return (
- <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
- <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
- {this.lightbox(path)}
- </div>)
+ <div id={id} className={`imageBox-cont${interactive}`}
+ // onPointerDown={this.onPointerDown}
+ onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <img id={id} src={paths[Math.min(paths.length, this._photoIndex)]}
+ style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }}
+ width={nativeWidth}
+ ref={this._imgRef}
+ onLoad={this.onLoad} />
+ {paths.length > 1 ? this.dots(paths) : (null)}
+ {this.lightbox(paths)}
+ </div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 1295266e5..20cae03d4 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -1,31 +1,123 @@
+@import "../globalCssVariables";
.keyValueBox-cont {
- overflow-y:scroll;
+ overflow-y: scroll;
+ width:100%;
height: 100%;
- border: black;
- border-width: 1px;
- border-style: solid;
+ background-color: $light-color;
+ border: 1px solid $intermediate-color;
+ border-radius: $border-radius;
box-sizing: border-box;
display: inline-block;
+ pointer-events: all;
.imageBox-cont img {
- max-height:45px;
- height: auto;
+ width: auto;
}
}
+$header-height: 30px;
+.keyValueBox-tbody {
+ width:100%;
+ height:100%;
+ position: absolute;
+ overflow-y: scroll;
+}
+.keyValueBox-key {
+ display: inline-block;
+ height:100%;
+ width:50%;
+ text-align: center;
+}
+.keyValueBox-fields {
+ display: inline-block;
+ height:100%;
+ width:50%;
+ text-align: center;
+}
+
.keyValueBox-table {
- position: relative;
+ position: absolute;
+ width:100%;
+ height:100%;
+ border-collapse: collapse;
+}
+.keyValueBox-td-key {
+ display:inline-block;
+ height:30px;
+}
+.keyValueBox-td-value {
+ display:inline-block;
+ height:30px;
+}
+.keyValueBox-valueRow {
+ width:100%;
+ height:30px;
+ display: inline-block;
}
.keyValueBox-header {
- background:gray;
+ width:100%;
+ position: relative;
+ display: inline-block;
+ background: $intermediate-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 12px;
+ height: $header-height;
+ padding-top: 4px;
+ th {
+ font-weight: normal;
+ &:first-child {
+ border-right: 1px solid $light-color;
+ }
+ }
}
+
.keyValueBox-evenRow {
- background: white;
+ position: relative;
+ display: inline-block;
+ width:100%;
+ height:$header-height;
+ background: $light-color;
.formattedTextBox-cont {
- background: white;
+ background: $light-color;
+ }
+}
+.keyValueBox-cont {
+ .collectionfreeformview-overlay {
+ position: relative;
}
}
+.keyValueBox-dividerDraggerThumb{
+ position: relative;
+ width: 4px;
+ float: left;
+ height: 30px;
+ width: 10px;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ border-radius: 10px;
+ background: gray;
+ pointer-events: all;
+}
+.keyValueBox-dividerDragger{
+ position: relative;
+ width: 100%;
+ float: left;
+ height: 37px;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ background: transparent;
+ pointer-events: none;
+}
+
.keyValueBox-oddRow {
- background: lightGray;
+ position: relative;
+ display: inline-block;
+ width:100%;
+ height:30px;
+ background: $light-color-secondary;
.formattedTextBox-cont {
- background: lightgray;
+ background: $light-color-secondary;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index ac8c949a9..86437a6c1 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -1,18 +1,54 @@
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { FieldWaiting } from '../../../fields/Field';
-import { KeyStore } from '../../../fields/KeyStore';
+import { CompileScript } from "../../util/Scripting";
import { FieldView, FieldViewProps } from './FieldView';
import "./KeyValueBox.scss";
import { KeyValuePair } from "./KeyValuePair";
-import React = require("react")
+import React = require("react");
+import { NumCast, Cast, FieldValue } from "../../../new_fields/Types";
+import { Doc, IsField } from "../../../new_fields/Doc";
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
+ private _mainCont = React.createRef<HTMLDivElement>();
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) }
+ public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
+ @observable private _keyInput: string = "";
+ @observable private _valueInput: string = "";
+ @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
+ get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document; }
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ }
+
+ @action
+ onEnterKey = (e: React.KeyboardEvent): void => {
+ if (e.key === 'Enter') {
+ if (this._keyInput && this._valueInput) {
+ let doc = this.fieldDocToLayout;
+ if (!doc) {
+ return;
+ }
+ let realDoc = doc;
+
+ let script = CompileScript(this._valueInput, { addReturn: true });
+ if (!script.compiled) {
+ return;
+ }
+ let res = script.run();
+ if (!res.success) return;
+ const field = res.result;
+ if (IsField(field)) {
+ realDoc[this._keyInput] = field;
+ }
+ this._keyInput = "";
+ this._valueInput = "";
+ }
+ }
+ }
onPointerDown = (e: React.PointerEvent): void => {
if (e.buttons === 1 && this.props.isSelected()) {
@@ -24,43 +60,87 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
createTable = () => {
- let doc = this.props.doc.GetT(KeyStore.Data, Document);
- if (!doc || doc == FieldWaiting) {
- return <tr><td>Loading...</td></tr>
+ let doc = this.fieldDocToLayout;
+ if (!doc) {
+ return <tr><td>Loading...</td></tr>;
}
let realDoc = doc;
let ids: { [key: string]: string } = {};
- let protos = doc.GetAllPrototypes();
+ let protos = Doc.GetAllPrototypes(doc);
for (const proto of protos) {
- proto._proxies.forEach((val, key) => {
+ Object.keys(proto).forEach(key => {
if (!(key in ids)) {
ids[key] = key;
}
- })
+ });
}
let rows: JSX.Element[] = [];
let i = 0;
for (let key in ids) {
- rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />)
+ rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} key={key} keyName={key} />);
}
return rows;
}
+ @action
+ keyChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._keyInput = e.currentTarget.value;
+ }
+
+ @action
+ valueChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._valueInput = e.currentTarget.value;
+ }
+
+ newKeyValue = () =>
+ (
+ <tr className="keyValueBox-valueRow">
+ <td className="keyValueBox-td-key" style={{ width: `${100 - this.splitPercentage}%` }}>
+ <input style={{ width: "100%" }} type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} />
+ </td>
+ <td className="keyValueBox-td-value" style={{ width: `${this.splitPercentage}%` }}>
+ <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} />
+ </td>
+ </tr>
+ )
+
+ @action
+ onDividerMove = (e: PointerEvent): void => {
+ let nativeWidth = this._mainCont.current!.getBoundingClientRect();
+ this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
+ }
+ @action
+ onDividerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onDividerMove);
+ document.removeEventListener('pointerup', this.onDividerUp);
+ }
+ onDividerDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ document.addEventListener("pointermove", this.onDividerMove);
+ document.addEventListener('pointerup', this.onDividerUp);
+ }
render() {
+ let dividerDragger = this.splitPercentage === 0 ? (null) :
+ <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
+ <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
+ </div>;
- return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}>
+ return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} ref={this._mainCont}>
<table className="keyValueBox-table">
- <tbody>
+ <tbody className="keyValueBox-tbody">
<tr className="keyValueBox-header">
- <th>Key</th>
- <th>Fields</th>
+ <th className="keyValueBox-key" style={{ width: `${100 - this.splitPercentage}%` }}>Key</th>
+ <th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>Fields</th>
</tr>
{this.createTable()}
+ {this.newKeyValue()}
</tbody>
</table>
- </div>)
+ {dividerDragger}
+ </div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
new file mode 100644
index 000000000..a1c5d5537
--- /dev/null
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -0,0 +1,37 @@
+@import "../globalCssVariables";
+
+
+.keyValuePair-td-key {
+ display:inline-block;
+ .keyValuePair-td-key-container{
+ width:100%;
+ height:100%;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ justify-content: space-between;
+ .keyValuePair-td-key-delete{
+ position: relative;
+ background-color: transparent;
+ color:red;
+ }
+ .keyValuePair-keyField {
+ width:100%;
+ text-align: center;
+ position: relative;
+ overflow: auto;
+ }
+ }
+}
+.keyValuePair-td-value {
+ display:inline-block;
+ overflow: scroll;
+ img {
+ max-height: 36px;
+ width: auto;
+ }
+ .videoBox-cont{
+ width: auto;
+ max-height: 36px;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index a97e98313..4f7919f50 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,58 +1,88 @@
+import { action, observable } from 'mobx';
+import { observer } from "mobx-react";
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import { emptyFunction, returnFalse, returnZero } from '../../../Utils';
+import { CompileScript } from "../../util/Scripting";
+import { Transform } from '../../util/Transform';
+import { EditableView } from "../EditableView";
+import { FieldView, FieldViewProps } from './FieldView';
import "./KeyValueBox.scss";
-import React = require("react")
-import { FieldViewProps, FieldView } from './FieldView';
-import { Opt, Field } from '../../../fields/Field';
-import { observer } from "mobx-react"
-import { observable, action } from 'mobx';
-import { Document } from '../../../fields/Document';
-import { Key } from '../../../fields/Key';
-import { Server } from "../../Server"
+import "./KeyValuePair.scss";
+import React = require("react");
+import { Doc, Opt, IsField } from '../../../new_fields/Doc';
+import { FieldValue } from '../../../new_fields/Types';
// Represents one row in a key value plane
export interface KeyValuePairProps {
rowStyle: string;
- fieldId: string;
- doc: Document;
+ keyName: string;
+ doc: Doc;
+ keyWidth: number;
}
@observer
export class KeyValuePair extends React.Component<KeyValuePairProps> {
- @observable
- private key: Opt<Key>
-
- constructor(props: KeyValuePairProps) {
- super(props);
- Server.GetField(this.props.fieldId,
- action((field: Opt<Field>) => {
- if (field) {
- this.key = field as Key;
- }
- }));
-
- }
-
-
render() {
- if (!this.key) {
- return <tr><td>error</td><td></td></tr>
-
- }
let props: FieldViewProps = {
- doc: this.props.doc,
- fieldKey: this.key,
- isSelected: () => false,
- select: () => { },
+ Document: this.props.doc,
+ ContainingCollectionView: undefined,
+ fieldKey: this.props.keyName,
+ isSelected: returnFalse,
+ select: emptyFunction,
isTopMost: false,
- bindings: {},
selectOnLoad: false,
- }
+ active: returnFalse,
+ whenActiveChanged: emptyFunction,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ PanelWidth: returnZero,
+ PanelHeight: returnZero,
+ };
+ let contents = <FieldView {...props} />;
+ let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
return (
<tr className={this.props.rowStyle}>
- <td>{this.key.Name}</td>
- <td><FieldView {...props} /></td>
+ <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
+ <div className="keyValuePair-td-key-container">
+ <button className="keyValuePair-td-key-delete" onClick={() => {
+ if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
+ props.Document[props.fieldKey] = undefined;
+ }
+ else props.Document.proto![props.fieldKey] = undefined;
+ }}>
+ X
+ </button>
+ <div className="keyValuePair-keyField">{fieldKey}</div>
+ </div>
+ </td>
+ <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }}>
+ <EditableView contents={contents} height={36} GetValue={() => {
+
+ let field = FieldValue(props.Document[props.fieldKey]);
+ if (field) {
+ //TODO Types
+ return String(field);
+ // return field.ToScriptString();
+ }
+ return "";
+ }}
+ SetValue={(value: string) => {
+ let script = CompileScript(value, { addReturn: true });
+ if (!script.compiled) {
+ return false;
+ }
+ let res = script.run();
+ if (!res.success) return false;
+ const field = res.result;
+ if (IsField(field)) {
+ props.Document[props.fieldKey] = field;
+ return true;
+ }
+ return false;
+ }}>
+ </EditableView></td>
</tr>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 00e5ebb3d..639f83b38 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -1,13 +1,14 @@
+@import "../globalCssVariables";
.link-container {
width: 100%;
- height: 30px;
+ height: 50px;
display: flex;
flex-direction: row;
border-top: 0.5px solid #bababa;
}
.info-container {
- width: 60%;
+ width: 65%;
padding-top: 5px;
padding-left: 5px;
display: flex;
@@ -23,17 +24,43 @@
}
.button-container {
- width: 40%;
+ width: 35%;
+ padding-top: 8px;
display: flex;
flex-direction: row;
}
.button {
- height: 15px;
- width: 15px;
- margin: 8px 5px;
+ height: 20px;
+ width: 20px;
+ margin: 8px 4px;
border-radius: 50%;
- opacity: 0.6;
+ opacity: 0.9;
pointer-events: auto;
- background-color: #2B6091;
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 60%;
+ transition: transform 0.2s;
+}
+
+.button:hover {
+ background: $main-accent;
+ cursor: pointer;
+}
+
+// .fa-icon-view {
+// margin-left: 3px;
+// margin-top: 5px;
+// }
+
+.fa-icon-edit {
+ margin-left: 6px;
+ margin-top: 6px;
+}
+
+.fa-icon-delete {
+ margin-left: 7px;
+ margin-top: 6px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 69df676ff..68b692aad 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,67 +1,61 @@
-import { observable, computed, action } from "mobx";
-import React = require("react");
-import { SelectionManager } from "../../util/SelectionManager";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { observer } from "mobx-react";
-import './LinkBox.scss'
-import { KeyStore } from '../../../fields/KeyStore'
-import { props } from "bluebird";
-import { DocumentView } from "./DocumentView";
-import { Document } from "../../../fields/Document";
-import { ListField } from "../../../fields/ListField";
import { DocumentManager } from "../../util/DocumentManager";
-import { LinkEditor } from "./LinkEditor";
+import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
+import './LinkBox.scss';
+import React = require("react");
+import { Doc } from '../../../new_fields/Doc';
+import { Cast, NumCast } from '../../../new_fields/Types';
+import { listSpec } from '../../../new_fields/Schema';
+import { action } from 'mobx';
+
+
+library.add(faEye);
+library.add(faEdit);
+library.add(faTimes);
interface Props {
- linkDoc: Document;
+ linkDoc: Doc;
linkName: String;
- pairedDoc: Document;
+ pairedDoc: Doc;
type: String;
- showEditor: () => void
+ showEditor: () => void;
}
@observer
export class LinkBox extends React.Component<Props> {
- onViewButtonPressed = (e: React.PointerEvent): void => {
- console.log("view down");
+ @undoBatch
+ onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
e.stopPropagation();
- let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc);
- if (docView) {
- docView.props.focus(this.props.pairedDoc);
- } else {
- CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc)
- }
+ DocumentManager.Instance.jumpToDocument(this.props.pairedDoc, e.altKey);
}
onEditButtonPressed = (e: React.PointerEvent): void => {
- console.log("edit down");
e.stopPropagation();
this.props.showEditor();
}
- onDeleteButtonPressed = (e: React.PointerEvent): void => {
- console.log("delete down");
+ @action
+ onDeleteButtonPressed = async (e: React.PointerEvent): Promise<void> => {
e.stopPropagation();
- this.props.linkDoc.GetTAsync(KeyStore.LinkedFromDocs, Document, field => {
- if (field) {
- field.GetTAsync<ListField<Document>>(KeyStore.LinkedToDocs, ListField, field => {
- if (field) {
- field.Data.splice(field.Data.indexOf(this.props.linkDoc));
- }
- })
+ const [linkedFrom, linkedTo] = await Promise.all([Cast(this.props.linkDoc.linkedFrom, Doc), Cast(this.props.linkDoc.linkedTo, Doc)]);
+ if (linkedFrom) {
+ const linkedToDocs = Cast(linkedFrom.linkedToDocs, listSpec(Doc));
+ if (linkedToDocs) {
+ linkedToDocs.splice(linkedToDocs.indexOf(this.props.linkDoc), 1);
}
- });
- this.props.linkDoc.GetTAsync(KeyStore.LinkedToDocs, Document, field => {
- if (field) {
- field.GetTAsync<ListField<Document>>(KeyStore.LinkedFromDocs, ListField, field => {
- if (field) {
- field.Data.splice(field.Data.indexOf(this.props.linkDoc));
- }
- })
+ }
+ if (linkedTo) {
+ const linkedFromDocs = Cast(linkedTo.linkedFromDocs, listSpec(Doc));
+ if (linkedFromDocs) {
+ linkedFromDocs.splice(linkedFromDocs.indexOf(this.props.linkDoc), 1);
}
- });
+ }
}
render() {
@@ -79,11 +73,14 @@ export class LinkBox extends React.Component<Props> {
</div>
<div className="button-container">
- <div className="button" onPointerDown={this.onViewButtonPressed}></div>
- <div className="button" onPointerDown={this.onEditButtonPressed}></div>
- <div className="button" onPointerDown={this.onDeleteButtonPressed}></div>
+ {/* <div title="Follow Link" className="button" onPointerDown={this.onViewButtonPressed}>
+ <FontAwesomeIcon className="fa-icon-view" icon="eye" size="sm" /></div> */}
+ <div title="Edit Link" className="button" onPointerDown={this.onEditButtonPressed}>
+ <FontAwesomeIcon className="fa-icon-edit" icon="edit" size="sm" /></div>
+ <div title="Delete Link" className="button" onPointerDown={this.onDeleteButtonPressed}>
+ <FontAwesomeIcon className="fa-icon-delete" icon="times" size="sm" /></div>
</div>
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index cb191dc8c..9629585d7 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -1,3 +1,4 @@
+@import "../globalCssVariables";
.edit-container {
width: 100%;
height: auto;
@@ -9,21 +10,33 @@
margin-bottom: 10px;
padding: 5px;
font-size: 12px;
+ border: 1px solid #bababa;
}
.description-input {
- font-size: 12px;
+ font-size: 11px;
padding: 5px;
margin-bottom: 10px;
+ border: 1px solid #bababa;
}
.save-button {
width: 50px;
- height: 20px;
- background-color: #2B6091;
+ height: 22px;
+ pointer-events: auto;
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ padding: 2px;
+ font-size: 10px;
margin: 0 auto;
- color: white;
+ transition: transform 0.2s;
text-align: center;
line-height: 20px;
- font-size: 12px;
+}
+
+.save-button:hover {
+ background: $main-accent;
+ cursor: pointer;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 3f7b4bf2d..71a423338 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -2,32 +2,31 @@ import { observable, computed, action } from "mobx";
import React = require("react");
import { SelectionManager } from "../../util/SelectionManager";
import { observer } from "mobx-react";
-import './LinkEditor.scss'
-import { KeyStore } from '../../../fields/KeyStore'
+import './LinkEditor.scss';
import { props } from "bluebird";
import { DocumentView } from "./DocumentView";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
import { link } from "fs";
+import { StrCast } from "../../../new_fields/Types";
+import { Doc } from "../../../new_fields/Doc";
interface Props {
- linkDoc: Document;
+ linkDoc: Doc;
showLinks: () => void;
}
@observer
export class LinkEditor extends React.Component<Props> {
- @observable private _nameInput: string = this.props.linkDoc.GetText(KeyStore.Title, "");
- @observable private _descriptionInput: string = this.props.linkDoc.GetText(KeyStore.LinkDescription, "");
+ @observable private _nameInput: string = StrCast(this.props.linkDoc.title);
+ @observable private _descriptionInput: string = StrCast(this.props.linkDoc.linkDescription);
onSaveButtonPressed = (e: React.PointerEvent): void => {
- console.log("view down");
e.stopPropagation();
- this.props.linkDoc.SetData(KeyStore.Title, this._nameInput, TextField);
- this.props.linkDoc.SetData(KeyStore.LinkDescription, this._descriptionInput, TextField);
+ let linkDoc = this.props.linkDoc.proto ? this.props.linkDoc.proto : this.props.linkDoc;
+ linkDoc.title = this._nameInput;
+ linkDoc.linkDescription = this._descriptionInput;
this.props.showLinks();
}
@@ -43,7 +42,7 @@ export class LinkEditor extends React.Component<Props> {
<div className="save-button" onPointerDown={this.onSaveButtonPressed}>SAVE</div>
</div>
- )
+ );
}
@action
diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss
index a120ab2a7..dedcce6ef 100644
--- a/src/client/views/nodes/LinkMenu.scss
+++ b/src/client/views/nodes/LinkMenu.scss
@@ -10,6 +10,7 @@
padding: 5px;
margin-bottom: 10px;
font-size: 12px;
+ border: 1px solid #bababa;
}
#linkMenu-list {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 5c6b06d00..4cf798249 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -1,53 +1,51 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from "../../../fields/ListField";
import { DocumentView } from "./DocumentView";
import { LinkBox } from "./LinkBox";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
+import { Id } from "../../../new_fields/RefField";
interface Props {
docView: DocumentView;
- changeFlyout: () => void
+ changeFlyout: () => void;
}
@observer
export class LinkMenu extends React.Component<Props> {
- @observable private _editingLink?: Document;
+ @observable private _editingLink?: Doc;
- renderLinkItems(links: Document[], key: Key, type: string) {
+ renderLinkItems(links: Doc[], key: string, type: string) {
return links.map(link => {
- let doc = link.GetT(key, Document);
- if (doc && doc != FieldWaiting) {
- return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />
+ let doc = FieldValue(Cast(link[key], Doc));
+ if (doc) {
+ return <LinkBox key={doc[Id]} linkDoc={link} linkName={StrCast(link.title)} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
}
- })
+ });
}
render() {
//get list of links from document
- let linkFrom: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []);
- let linkTo: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []);
+ let linkFrom = DocListCast(this.props.docView.props.Document.linkedFromDocs);
+ let linkTo = DocListCast(this.props.docView.props.Document.linkedToDocs);
if (this._editingLink === undefined) {
return (
<div id="linkMenu-container">
- <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input>
+ {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */}
<div id="linkMenu-list">
- {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Source: ")}
- {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Destination: ")}
+ {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")}
+ {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")}
</div>
</div>
- )
+ );
} else {
return (
<LinkEditor linkDoc={this._editingLink} showLinks={action(() => this._editingLink = undefined)}></LinkEditor>
- )
+ );
}
}
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 9f92410d4..3760e378a 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,15 +1,37 @@
.react-pdf__Page {
transform-origin: left top;
position: absolute;
+ top: 0;
+ left:0;
+}
+.react-pdf__Page__textContent span {
+ user-select: text;
}
.react-pdf__Document {
position: absolute;
}
.pdfBox-buttonTray {
position:absolute;
+ top: 0;
+ left:0;
z-index: 25;
+ pointer-events: all;
+}
+.pdfButton {
+ pointer-events: all;
+ width: 100px;
+ height:100px;
+}
+.pdfBox-cont {
+ pointer-events: none ;
+ span {
+ pointer-events: none !important;
+ }
+}
+.pdfBox-cont-interactive {
+ pointer-events: all;
}
.pdfBox-contentContainer {
position: absolute;
- transform-origin: "left top";
+ transform-origin: left top;
} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 70a70c7c8..e71ac4924 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,22 +1,26 @@
import * as htmlToImage from "html-to-image";
-import { action, computed, observable, reaction, IReactionDisposer } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace, runInAction } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css';
import Measure from "react-measure";
//@ts-ignore
import { Document, Page } from "react-pdf";
import 'react-pdf/dist/Page/AnnotationLayer.css';
-import { FieldWaiting, Opt } from '../../../fields/Field';
-import { ImageField } from '../../../fields/ImageField';
-import { KeyStore } from '../../../fields/KeyStore';
-import { PDFField } from '../../../fields/PDFField';
+import { RouteStore } from "../../../server/RouteStore";
import { Utils } from '../../../Utils';
import { Annotation } from './Annotation';
import { FieldView, FieldViewProps } from './FieldView';
-import "./ImageBox.scss";
import "./PDFBox.scss";
-import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
-import React = require("react")
+import React = require("react");
+import { SelectionManager } from "../../util/SelectionManager";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Opt } from "../../../new_fields/Doc";
+import { DocComponent } from "../DocComponent";
+import { makeInterface } from "../../../new_fields/Schema";
+import { positionSchema } from "./DocumentView";
+import { pageSchema } from "./ImageBox";
+import { ImageField, PdfField } from "../../../new_fields/URLField";
+import { InkingControl } from "../InkingControl";
/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
* This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
@@ -38,121 +42,58 @@ import React = require("react")
* 4) another method: click on highlight first and then drag on your desired text
* 5) To make another highlight, you need to reclick on the button
*
- * Draw:
- * 1) click draw and select color. then just draw like there's no tomorrow.
- * 2) once you finish drawing your masterpiece, just reclick on the draw button to end your drawing session.
- *
- * Pagination:
- * 1) click on arrows. You'll notice that stickies will stay in those page. But... highlights won't.
- * 2) to test this out, make few area/stickies and then click on next page then come back. You'll see that they are all saved.
- *
- *
* written by: Andrew Kim
*/
-@observer
-export class PDFBox extends React.Component<FieldViewProps> {
- public static LayoutString() { return FieldView.LayoutString(PDFBox); }
- private _mainDiv = React.createRef<HTMLDivElement>()
- private _pdf = React.createRef<HTMLCanvasElement>();
+type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
+const PdfDocument = makeInterface(positionSchema, pageSchema);
- //very useful for keeping track of X and y position throughout the PDF Canvas
- private initX: number = 0;
- private initY: number = 0;
- private initPage: boolean = false;
-
- //checks if tool is on
- private _toolOn: boolean = false; //checks if tool is on
- private _pdfContext: any = null; //gets pdf context
- private bool: Boolean = false; //general boolean debounce
- private currSpan: any;//keeps track of current span (for highlighting)
+@observer
+export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
+ public static LayoutString() { return FieldView.LayoutString(PDFBox); }
- private _currTool: any; //keeps track of current tool button reference
- private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool
- private _drawTool = React.createRef<HTMLButtonElement>()//drawing tool button reference
+ private _mainDiv = React.createRef<HTMLDivElement>();
+ private renderHeight = 2400;
- private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
- private _currColor: string = "black"; //current color that user selected (for ink/pen)
+ @observable private _renderAsSvg = true;
+ @observable private _alt = false;
- private _highlightTool = React.createRef<HTMLButtonElement>(); //highlighter button reference
- private _highlightToolOn: boolean = false;
- private _pdfCanvas: any;
- private _reactionDisposer: Opt<IReactionDisposer>;
+ private _reactionDisposer?: IReactionDisposer;
@observable private _perPageInfo: Object[] = []; //stores pageInfo
@observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno
- @observable private _currAnno: any = []
+ @observable private _currAnno: any = [];
@observable private _interactive: boolean = false;
@observable private _loaded: boolean = false;
- @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 0); }
+ @computed private get curPage() { return NumCast(this.Document.curPage, 1); }
+ @computed private get thumbnailPage() { return NumCast(this.props.Document.thumbnailPage, -1); }
componentDidMount() {
+ let wasSelected = false;
this._reactionDisposer = reaction(
- () => this.curPage,
+ () => this.props.isSelected(),
() => {
- if (this.curPage && this.initPage) {
+ if (this.curPage > 0 && this.curPage !== this.thumbnailPage && wasSelected && !this.props.isSelected()) {
this.saveThumbnail();
- this._interactive = true;
- } else {
- if (this.curPage)
- this.initPage = true;
}
+ wasSelected = this._interactive = this.props.isSelected();
},
{ fireImmediately: true });
}
componentWillUnmount() {
- if (this._reactionDisposer) {
- this._reactionDisposer();
- }
- }
-
- /**
- * selection tool used for area highlighting (stickies). Kinda temporary
- */
- selectionTool = () => {
- this._toolOn = true;
- }
- /**
- * when user draws on the canvas. When mouse pointer is down
- */
- drawDown = (e: PointerEvent) => {
- this.initX = e.offsetX;
- this.initY = e.offsetY;
- this._pdfContext.beginPath();
- this._pdfContext.lineTo(this.initX, this.initY);
- this._pdfContext.strokeStyle = this._currColor;
- this._pdfCanvas.addEventListener("pointermove", this.drawMove);
- this._pdfCanvas.addEventListener("pointerup", this.drawUp);
-
- }
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = this.initX += e.movementX,
- y = this.initY += e.movementY;
- //connects the point
- this._pdfContext.lineTo(x, y);
- this._pdfContext.stroke();
+ if (this._reactionDisposer) this._reactionDisposer();
}
- drawUp = (e: PointerEvent) => {
- this._pdfContext.closePath();
- this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
- this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
- this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
- }
-
-
/**
* highlighting helper function
*/
makeEditableAndHighlight = (colour: string) => {
var range, sel = window.getSelection();
- if (sel.rangeCount && sel.getRangeAt) {
+ if (sel && sel.rangeCount && sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
@@ -160,31 +101,31 @@ export class PDFBox extends React.Component<FieldViewProps> {
document.execCommand("HiliteColor", false, colour);
}
- if (range) {
+ if (range && sel) {
sel.removeAllRanges();
sel.addRange(range);
let obj: Object = { parentDivs: [], spans: [] };
//@ts-ignore
- if (range.commonAncestorContainer.className == 'react-pdf__Page__textContent') { //multiline highlighting case
- obj = this.highlightNodes(range.commonAncestorContainer.childNodes)
+ if (range.commonAncestorContainer.className === 'react-pdf__Page__textContent') { //multiline highlighting case
+ obj = this.highlightNodes(range.commonAncestorContainer.childNodes);
} else { //single line highlighting case
- let parentDiv = range.commonAncestorContainer.parentElement
+ let parentDiv = range.commonAncestorContainer.parentElement;
if (parentDiv) {
- if (parentDiv.className == 'react-pdf__Page__textContent') { //when highlight is overwritten
- obj = this.highlightNodes(parentDiv.childNodes)
+ if (parentDiv.className === 'react-pdf__Page__textContent') { //when highlight is overwritten
+ obj = this.highlightNodes(parentDiv.childNodes);
} else {
parentDiv.childNodes.forEach((child) => {
- if (child.nodeName == 'SPAN') {
+ if (child.nodeName === 'SPAN') {
//@ts-ignore
- obj.parentDivs.push(parentDiv)
+ obj.parentDivs.push(parentDiv);
//@ts-ignore
- child.id = "highlighted"
+ child.id = "highlighted";
//@ts-ignore
- obj.spans.push(child)
- child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ obj.spans.push(child);
+ // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
- })
+ });
}
}
}
@@ -195,21 +136,21 @@ export class PDFBox extends React.Component<FieldViewProps> {
}
highlightNodes = (nodes: NodeListOf<ChildNode>) => {
- let temp = { parentDivs: [], spans: [] }
+ let temp = { parentDivs: [], spans: [] };
nodes.forEach((div) => {
div.childNodes.forEach((child) => {
- if (child.nodeName == 'SPAN') {
+ if (child.nodeName === 'SPAN') {
//@ts-ignore
- temp.parentDivs.push(div)
+ temp.parentDivs.push(div);
//@ts-ignore
- child.id = "highlighted"
+ child.id = "highlighted";
//@ts-ignore
- temp.spans.push(child)
- child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ temp.spans.push(child);
+ // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
- })
+ });
- })
+ });
return temp;
}
@@ -222,29 +163,27 @@ export class PDFBox extends React.Component<FieldViewProps> {
let index: any;
this._pageInfo.divs.forEach((obj: any) => {
obj.spans.forEach((element: any) => {
- if (element == span) {
- if (!index) {
- index = this._pageInfo.divs.indexOf(obj);
- }
+ if (element === span && !index) {
+ index = this._pageInfo.divs.indexOf(obj);
}
- })
- })
+ });
+ });
if (this._pageInfo.anno.length >= index + 1) {
- if (this._currAnno.length == 0) {
+ if (this._currAnno.length === 0) {
this._currAnno.push(this._pageInfo.anno[index]);
}
} else {
- if (this._currAnno.length == 0) { //if there are no current annotation
+ if (this._currAnno.length === 0) { //if there are no current annotation
let div = span.offsetParent;
//@ts-ignore
- let divX = div.style.left
+ let divX = div.style.left;
//@ts-ignore
- let divY = div.style.top
+ let divY = div.style.top;
//slicing "px" from the end
divX = divX.slice(0, divX.length - 2); //gets X of the DIV element (parent of Span)
divY = divY.slice(0, divY.length - 2); //gets Y of the DIV element (parent of Span)
- let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />
+ let annotation = <Annotation key={Utils.GenerateGuid()} Span={span} X={divX} Y={divY - 300} Highlights={this._pageInfo.divs} Annotations={this._pageInfo.anno} CurrAnno={this._currAnno} />;
this._pageInfo.anno.push(annotation);
this._currAnno.push(annotation);
}
@@ -262,7 +201,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
this.makeEditableAndHighlight(color);
}
} catch (ex) {
- this.makeEditableAndHighlight(color)
+ this.makeEditableAndHighlight(color);
}
}
}
@@ -271,11 +210,21 @@ export class PDFBox extends React.Component<FieldViewProps> {
* controls the area highlighting (stickies) Kinda temporary
*/
onPointerDown = (e: React.PointerEvent) => {
- if (this._toolOn) {
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
-
+ if (this.props.isSelected() && !InkingControl.Instance.selectedTool && e.buttons === 1) {
+ if (e.altKey) {
+ this._alt = true;
+ } else {
+ if (e.metaKey) {
+ e.stopPropagation();
+ }
+ }
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ if (this.props.isSelected() && e.buttons === 2) {
+ runInAction(() => this._alt = true);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
}
}
@@ -283,135 +232,40 @@ export class PDFBox extends React.Component<FieldViewProps> {
* controls area highlighting and partially highlighting. Kinda temporary
*/
@action
- onPointerUp = (e: React.PointerEvent) => {
- if (this._highlightToolOn) {
+ onPointerUp = (e: PointerEvent) => {
+ this._alt = false;
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (this.props.isSelected()) {
this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color.
- this._highlightToolOn = false;
- }
- if (this._toolOn) {
- let mouse = e.nativeEvent;
- let finalX = mouse.offsetX;
- let finalY = mouse.offsetY;
- let width = Math.abs(finalX - this.initX); //width
- let height = Math.abs(finalY - this.initY); //height
-
- //these two if statements are bidirectional dragging. You can drag from any point to another point and generate sticky
- if (finalX < this.initX) {
- this.initX = finalX;
- }
- if (finalY < this.initY) {
- this.initY = finalY;
- }
-
- if (this._mainDiv.current) {
- let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />
- this._pageInfo.area.push(sticky);
- }
- this._toolOn = false;
}
this._interactive = true;
}
- /**
- * starts drawing the line when user presses down.
- */
- onDraw = () => {
- if (this._currTool != null) {
- this._currTool.style.backgroundColor = "grey";
- }
-
- if (this._drawTool.current) {
- this._currTool = this._drawTool.current;
- if (this._drawToolOn) {
- this._drawToolOn = false;
- this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
- this._pdfCanvas.removeEventListener("pointerup", this.drawUp);
- this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
- this._drawTool.current.style.backgroundColor = "grey";
- } else {
- this._drawToolOn = true;
- this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
- this._drawTool.current.style.backgroundColor = "cyan";
- }
- }
- }
-
-
- /**
- * for changing color (for ink/pen)
- */
- onColorChange = (e: React.PointerEvent) => {
- if (e.currentTarget.innerHTML == "Red") {
- this._currColor = "red";
- } else if (e.currentTarget.innerHTML == "Blue") {
- this._currColor = "blue";
- } else if (e.currentTarget.innerHTML == "Green") {
- this._currColor = "green";
- } else if (e.currentTarget.innerHTML == "Black") {
- this._currColor = "black";
- }
-
- }
-
-
- /**
- * For highlighting (text drag highlighting)
- */
- onHighlight = () => {
- this._drawToolOn = false;
- if (this._currTool != null) {
- this._currTool.style.backgroundColor = "grey";
- }
- if (this._highlightTool.current) {
- this._currTool = this._drawTool.current;
- if (this._highlightToolOn) {
- this._highlightToolOn = false;
- this._highlightTool.current.style.backgroundColor = "grey";
- } else {
- this._highlightToolOn = true;
- this._highlightTool.current.style.backgroundColor = "orange";
- }
- }
- }
-
@action
saveThumbnail = () => {
+ this._renderAsSvg = false;
setTimeout(() => {
- var me = this;
- htmlToImage.toPng(this._mainDiv.current!,
- { width: me.props.doc.GetNumber(KeyStore.NativeWidth, 0), height: me.props.doc.GetNumber(KeyStore.NativeHeight, 0), quality: 0.5 })
- .then(function (dataUrl: string) {
- me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
- })
+ let nwidth = FieldValue(this.Document.nativeWidth, 0);
+ let nheight = FieldValue(this.Document.nativeHeight, 0);
+ htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 })
+ .then(action((dataUrl: string) => {
+ this.props.Document.thumbnail = new ImageField(new URL(dataUrl));
+ this.props.Document.thumbnailPage = FieldValue(this.Document.curPage, -1);
+ this._renderAsSvg = true;
+ }))
.catch(function (error: any) {
console.error('oops, something went wrong!', error);
});
- }, 1000);
+ }, 1250);
}
@action
onLoaded = (page: any) => {
- if (this._mainDiv.current) {
- this._mainDiv.current.childNodes.forEach((element) => {
- if (element.nodeName == "DIV") {
- element.childNodes[0].childNodes.forEach((e) => {
-
- if (e instanceof HTMLCanvasElement) {
- this._pdfCanvas = e;
- this._pdfContext = e.getContext("2d")
-
- }
-
- })
- }
- })
- }
-
// bcz: the number of pages should really be set when the document is imported.
- this.props.doc.SetNumber(KeyStore.NumPages, page._transport.numPages);
- if (this._perPageInfo.length == 0) { //Makes sure it only runs once
- this._perPageInfo = [...Array(page._transport.numPages)]
+ this.props.Document.numPages = page._transport.numPages;
+ if (this._perPageInfo.length === 0) { //Makes sure it only runs once
+ this._perPageInfo = [...Array(page._transport.numPages)];
}
this._loaded = true;
}
@@ -419,34 +273,40 @@ export class PDFBox extends React.Component<FieldViewProps> {
@action
setScaling = (r: any) => {
// bcz: the nativeHeight should really be set when the document is imported.
- // also, the native dimensions could be different for different pages of the PDF
+ // also, the native dimensions could be different for different pages of the canvas
// so this design is flawed.
- var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0);
- if (!this.props.doc.GetNumber(KeyStore.NativeHeight, 0)) {
- this.props.doc.SetNumber(KeyStore.NativeHeight, nativeWidth * r.entry.height / r.entry.width);
- }
- if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) {
- this.saveThumbnail();
+ var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
+ if (!FieldValue(this.Document.nativeHeight, 0)) {
+ var nativeHeight = nativeWidth * r.offset.height / r.offset.width;
+ this.props.Document.height = nativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
+ this.props.Document.nativeHeight = nativeHeight;
}
}
-
+ @computed
+ get pdfPage() {
+ return <Page height={this.renderHeight} renderTextLayer={false} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />;
+ }
@computed
get pdfContent() {
- let page = this.curPage;
- if (page == 0)
- page = 1;
- const renderHeight = 2400;
- let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
- let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
+ trace();
+ let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
+ if (!pdfUrl) {
+ return <p>No pdf url to render</p>;
+ }
+ let pdfpage = this.pdfPage;
+ let body = this.Document.nativeHeight ?
+ pdfpage :
+ <Measure offset onResize={this.setScaling}>
+ {({ measureRef }) =>
+ <div className="pdfBox-page" ref={measureRef}>
+ {pdfpage}
+ </div>
+ }
+ </Measure>;
+ let xf = (this.Document.nativeHeight || 0) / this.renderHeight;
return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
- <Document file={window.origin + "/corsProxy/" + `${pdfUrl}`}>
- <Measure onResize={this.setScaling}>
- {({ measureRef }) =>
- <div className="pdfBox-page" ref={measureRef}>
- <Page height={renderHeight} pageNumber={page} onLoadSuccess={this.onLoaded} />
- </div>
- }
- </Measure>
+ <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl.url}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}>
+ {body}
</Document>
</div >;
}
@@ -454,34 +314,35 @@ export class PDFBox extends React.Component<FieldViewProps> {
@computed
get pdfRenderer() {
let proxy = this._loaded ? (null) : this.imageProxyRenderer;
- let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
- if ((!this._interactive && proxy) || !pdfUrl || pdfUrl == FieldWaiting) {
+ let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
+ if ((!this._interactive && proxy) || !pdfUrl) {
return proxy;
}
return [
this._pageInfo.area.filter(() => this._pageInfo.area).map((element: any) => element),
this._currAnno.map((element: any) => element),
- <div key="pdfBox-contentShell">
- {this.pdfContent}
- {proxy}
- </div>
+ this.pdfContent,
+ proxy
];
}
@computed
get imageProxyRenderer() {
- let field = this.props.doc.Get(KeyStore.Thumbnail);
- if (field) {
- let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
- field instanceof ImageField ? field.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
+ let thumbField = this.props.Document.thumbnail;
+ if (thumbField) {
+ let path = this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
return <img src={path} width="100%" />;
}
return (null);
}
-
+ @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true);
+ @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false);
render() {
+ trace();
+ let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
return (
- <div className="pdfBox-cont" ref={this._mainDiv} onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} >
+ <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} >
{this.pdfRenderer}
</div >
);
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
deleted file mode 100644
index d57dd5c0b..000000000
--- a/src/client/views/nodes/Sticky.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import React = require("react")
-import { observer } from "mobx-react"
-import 'react-pdf/dist/Page/AnnotationLayer.css'
-
-interface IProps {
- Height: number;
- Width: number;
- X: number;
- Y: number;
-}
-
-/**
- * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
- * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
- *
- * Written By: Andrew Kim
- */
-@observer
-export class Sticky extends React.Component<IProps> {
-
- private initX: number = 0;
- private initY: number = 0;
-
- private _ref = React.createRef<HTMLCanvasElement>();
- private ctx: any; //context that keeps track of sticky canvas
-
- /**
- * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
- */
- drawDown = (e: React.PointerEvent) => {
- if (this._ref.current) {
- this.ctx = this._ref.current.getContext("2d");
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
- this.ctx.beginPath();
- this.ctx.lineTo(this.initX, this.initY);
- this.ctx.strokeStyle = "black";
- document.addEventListener("pointermove", this.drawMove);
- document.addEventListener("pointerup", this.drawUp);
- }
- }
-
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = this.initX += e.movementX,
- y = this.initY += e.movementY;
- //connects the point
- this.ctx.lineTo(x, y);
- this.ctx.stroke();
-
- }
-
- /**
- * when user lifts the mouse, the drawing ends
- */
- drawUp = (e: PointerEvent) => {
- this.ctx.closePath();
- console.log(this.ctx);
- document.removeEventListener("pointermove", this.drawMove);
- }
-
- render() {
- return (
- <div onPointerDown={this.drawDown}>
- <canvas ref={this._ref} height={this.props.Height} width={this.props.Width}
- style={{
- position: "absolute",
- top: "20px",
- left: "0px",
- zIndex: 1,
- background: "yellow",
- transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
- opacity: 0.4
- }}
- />
-
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 7306450d9..35db64cf4 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,4 +1,8 @@
-.videobox-cont{
+.videoBox-cont, .videoBox-cont-fullScreen{
width: 100%;
- height: 100%;
+ height: Auto;
+}
+
+.videoBox-cont-fullScreen {
+ pointer-events: all;
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 22ff5c5ad..81c429a02 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,43 +1,163 @@
-import React = require("react")
-import { FieldViewProps, FieldView } from './FieldView';
-import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
-import { VideoField } from '../../../fields/VideoField';
-import "./VideoBox.scss"
-import { ContextMenu } from "../../views/ContextMenu";
-import { observable, action } from 'mobx';
-import { KeyStore } from '../../../fields/KeyStore';
+import React = require("react");
+import { observer } from "mobx-react";
+import { FieldView, FieldViewProps } from './FieldView';
+import * as rp from "request-promise";
+import "./VideoBox.scss";
+import { action, IReactionDisposer, reaction, observable } from "mobx";
+import { DocComponent } from "../DocComponent";
+import { positionSchema } from "./DocumentView";
+import { makeInterface } from "../../../new_fields/Schema";
+import { pageSchema } from "./ImageBox";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
+import Measure from "react-measure";
+import "./VideoBox.scss";
+import { RouteStore } from "../../../server/RouteStore";
+import { DocServer } from "../../DocServer";
+
+type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
+const VideoDocument = makeInterface(positionSchema, pageSchema);
@observer
-export class VideoBox extends React.Component<FieldViewProps> {
+export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+ private _reactionDisposer?: IReactionDisposer;
+ private _videoRef: HTMLVideoElement | null = null;
+ private _loaded: boolean = false;
+ @observable _playTimer?: NodeJS.Timeout = undefined;
+ @observable _fullScreen = false;
+ @observable public Playing: boolean = false;
+ public static LayoutString() { return FieldView.LayoutString(VideoBox); }
+
+ public get player(): HTMLVideoElement | undefined {
+ if (this._videoRef) {
+ return this._videoRef;
+ }
+ }
+ @action
+ setScaling = (r: any) => {
+ if (this._loaded) {
+ // bcz: the nativeHeight should really be set when the document is imported.
+ var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
+ var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
+ var newNativeHeight = nativeWidth * r.offset.height / r.offset.width;
+ if (!nativeHeight && newNativeHeight !== nativeHeight && !isNaN(newNativeHeight)) {
+ this.Document.height = newNativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
+ this.Document.nativeHeight = newNativeHeight;
+ }
+ } else {
+ this._loaded = true;
+ }
+ }
- public static LayoutString() { return FieldView.LayoutString(VideoBox) }
+ @action public Play() {
+ this.Playing = true;
+ if (this.player) this.player.play();
+ if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 1000);
+ }
+
+ @action public Pause() {
+ this.Playing = false;
+ if (this.player) this.player.pause();
+ if (this._playTimer) {
+ clearInterval(this._playTimer);
+ this._playTimer = undefined;
+ }
+ }
- constructor(props: FieldViewProps) {
- super(props);
+ @action public FullScreen() {
+ this._fullScreen = true;
+ this.player && this.player.requestFullscreen();
}
-
+ @action
+ updateTimecode = () => this.player && (this.props.Document.curPage = this.player.currentTime)
componentDidMount() {
+ if (this.props.setVideoBox) this.props.setVideoBox(this);
}
-
componentWillUnmount() {
+ this.Pause();
+ if (this._reactionDisposer) this._reactionDisposer();
+ }
+
+ @action
+ setVideoRef = (vref: HTMLVideoElement | null) => {
+ this._videoRef = vref;
+ if (vref) {
+ vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
+ if (this._reactionDisposer) this._reactionDisposer();
+ this._reactionDisposer = reaction(() => this.props.Document.curPage, () =>
+ vref.currentTime = NumCast(this.props.Document.curPage, 0), { fireImmediately: true });
+ }
+ }
+ videoContent(path: string) {
+ let style = "videoBox-cont" + (this._fullScreen ? "-fullScreen" : "");
+ return <video className={`${style}`} ref={this.setVideoRef} onPointerDown={this.onPointerDown}>
+ <source src={path} type="video/mp4" />
+ Not supported.
+ </video>;
+ }
+
+ getMp4ForVideo(videoId: string = "JN5beCVArMs") {
+ return new Promise(async (resolve, reject) => {
+ const videoInfoRequestConfig = {
+ headers: {
+ connection: 'keep-alive',
+ "user-agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:43.0) Gecko/20100101 Firefox/46.0',
+ },
+
+ };
+ try {
+ let responseSchema: any = {};
+ const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig);
+ const dataHtml = videoInfoResponse;
+ const start = dataHtml.indexOf('ytplayer.config = ') + 18;
+ const end = dataHtml.indexOf(';ytplayer.load');
+ const subString = dataHtml.substring(start, end);
+ const subJson = JSON.parse(subString);
+ const stringSub = subJson.args.player_response;
+ const stringSubJson = JSON.parse(stringSub);
+ const adaptiveFormats = stringSubJson.streamingData.adaptiveFormats;
+ const videoDetails = stringSubJson.videoDetails;
+ responseSchema.adaptiveFormats = adaptiveFormats;
+ responseSchema.videoDetails = videoDetails;
+ resolve(responseSchema);
+ }
+ catch (err) {
+ console.log(`
+ --- Youtube ---
+ Function: getMp4ForVideo
+ Error: `, err);
+ reject(err);
+ }
+ });
+ }
+ onPointerDown = (e: React.PointerEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
}
-
render() {
- let field = this.props.doc.Get(this.props.fieldKey)
- let path = field == FieldWaiting ? "http://techslides.com/demos/sample-videos/small.mp4":
- field instanceof VideoField ? field.Data.href : "http://techslides.com/demos/sample-videos/small.mp4";
-
- return (
- <div>
- <video width = {200} height = {200} controls className = "videobox-cont">
- <source src = {path} type = "video/mp4"/>
- Not supported.
- </video>
- </div>
- )
+ let field = Cast(this.Document[this.props.fieldKey], VideoField);
+ if (!field) {
+ return <div>Loading</div>;
+ }
+
+ // this.getMp4ForVideo().then((mp4) => {
+ // console.log(mp4);
+ // }).catch(e => {
+ // console.log("")
+ // });
+ // //
+ let content = this.videoContent(field.url.href);
+ return NumCast(this.props.Document.nativeHeight) ?
+ content :
+ <Measure offset onResize={this.setScaling}>
+ {({ measureRef }) =>
+ <div style={{ width: "100%", height: "auto" }} ref={measureRef}>
+ {content}
+ </div>
+ }
+ </Measure>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index a535b2638..eb09b0693 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,10 +1,31 @@
-.webBox-cont {
+.webBox-cont, .webBox-cont-interactive{
padding: 0vw;
position: absolute;
+ top: 0;
+ left:0;
width: 100%;
height: 100%;
- overflow: scroll;
+ overflow: auto;
+ pointer-events: none ;
+}
+.webBox-cont-interactive {
+ pointer-events: all;
+ span {
+ user-select: text !important;
+ }
+}
+
+#webBox-htmlSpan {
+ position: absolute;
+ top:0;
+ left:0;
+}
+
+.webBox-overlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
}
.webBox-button {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2ca8d49ce..2239a8e38 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,38 +1,59 @@
import "./WebBox.scss";
-import React = require("react")
-import { WebField } from '../../../fields/WebField';
+import React = require("react");
import { FieldViewProps, FieldView } from './FieldView';
-import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
-import { computed } from 'mobx';
-import { KeyStore } from '../../../fields/KeyStore';
+import { HtmlField } from "../../../new_fields/HtmlField";
+import { WebField } from "../../../new_fields/URLField";
+import { observer } from "mobx-react";
+import { computed, reaction, IReactionDisposer } from 'mobx';
+import { DocumentDecorations } from "../DocumentDecorations";
+import { InkingControl } from "../InkingControl";
@observer
export class WebBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(WebBox); }
- constructor(props: FieldViewProps) {
- super(props);
+ _ignore = 0;
+ onPreWheel = (e: React.WheelEvent) => {
+ this._ignore = e.timeStamp;
+ }
+ onPrePointer = (e: React.PointerEvent) => {
+ this._ignore = e.timeStamp;
+ }
+ onPostPointer = (e: React.PointerEvent) => {
+ if (this._ignore !== e.timeStamp) {
+ e.stopPropagation();
+ }
+ }
+ onPostWheel = (e: React.WheelEvent) => {
+ if (this._ignore !== e.timeStamp) {
+ e.stopPropagation();
+ }
}
-
- @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); }
-
render() {
- let field = this.props.doc.Get(this.props.fieldKey);
- let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
- field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
-
- let content = this.html ?
- <span dangerouslySetInnerHTML={{ __html: this.html }}></span> :
- <div style={{ width: "100%", height: "100%", position: "absolute" }}>
- <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }}></iframe>
- {this.props.isSelected() ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ let field = this.props.Document[this.props.fieldKey];
+ let view;
+ if (field instanceof HtmlField) {
+ view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
+ } else if (field instanceof WebField) {
+ view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+ } else {
+ view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+ }
+ let content =
+ <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
+ {view}
</div>;
+ let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+
+ let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
return (
- <div className="webBox-cont" >
- {content}
- </div>)
+ <>
+ <div className={classname} >
+ {content}
+ </div>
+ {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
+ </>);
}
} \ No newline at end of file
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 7bc70615f..57221aa39 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -1,46 +1,83 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
+import { SerializationHelper } from '../client/util/SerializationHelper';
+import { createSchema, makeInterface, makeStrictInterface, listSpec } from '../new_fields/Schema';
+import { ImageField } from '../new_fields/URLField';
+import { Doc } from '../new_fields/Doc';
+import { List } from '../new_fields/List';
-class TestInternal extends React.Component {
- onContextMenu = (e: React.MouseEvent) => {
- console.log("Internal");
- e.stopPropagation();
- }
- onPointerDown = (e: React.MouseEvent) => {
- console.log("pointer down")
- e.preventDefault();
- }
+const schema1 = createSchema({
+ hello: "number",
+ test: "string",
+ fields: "boolean",
+ url: ImageField,
+ testDoc: Doc
+});
- render() {
- return <div onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}
- onPointerUp={this.onPointerDown}>Hello world</div>
- }
-}
+type TestDoc = makeInterface<[typeof schema1]>;
+const TestDoc: (doc?: Doc) => TestDoc = makeInterface(schema1);
-class TestChild extends React.Component {
- onContextMenu = () => {
- console.log("Child");
- }
+const schema2 = createSchema({
+ hello: ImageField,
+ test: "boolean",
+ fields: listSpec("number"),
+ url: "number",
+ testDoc: ImageField
+});
- render() {
- return <div onContextMenu={this.onContextMenu}><TestInternal /></div>
- }
-}
+const Test2Doc = makeStrictInterface(schema2);
+type Test2Doc = makeStrictInterface<typeof schema2>;
+
+const assert = (bool: boolean) => {
+ if (!bool) throw new Error();
+};
+
+class Test extends React.Component {
+ onClick = () => {
+ const url = new ImageField(new URL("http://google.com"));
+ const doc = new Doc();
+ const doc2 = new Doc();
+ doc.hello = 5;
+ doc.fields = "test";
+ doc.test = "hello doc";
+ doc.url = url;
+ //doc.testDoc = doc2;
+
+
+ const test1: TestDoc = TestDoc(doc);
+ assert(test1.hello === 5);
+ assert(test1.fields === undefined);
+ assert(test1.test === "hello doc");
+ assert(test1.url === url);
+ assert(test1.testDoc === doc2);
+ test1.myField = 20;
+ assert(test1.myField === 20);
-class TestParent extends React.Component {
- onContextMenu = () => {
- console.log("Parent");
+ const test2: Test2Doc = Test2Doc(doc);
+ assert(test2.hello === undefined);
+ // assert(test2.fields === "test");
+ assert(test2.test === undefined);
+ assert(test2.url === undefined);
+ assert(test2.testDoc === undefined);
+ test2.url = 35;
+ assert(test2.url === 35);
+ const l = new List<Doc>();
+ //TODO push, and other array functions don't go through the proxy
+ l.push(doc2);
+ //TODO currently length, and any other string fields will get serialized
+ doc.list = l;
+ console.log(l.slice());
}
render() {
- return <div onContextMenu={this.onContextMenu}><TestChild /></div>
+ return <div><button onClick={this.onClick}>Click me</button>
+ {/* <input onKeyPress={this.onEnter}></input> */}
+ </div>;
}
}
-ReactDOM.render((
- <div style={{ position: "absolute", width: "100%", height: "100%" }}>
- <TestParent />
- </div>),
+ReactDOM.render(
+ <Test />,
document.getElementById('root')
); \ No newline at end of file
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 780e9f8f2..4cac09dee 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -3,190 +3,184 @@ import "normalize.css";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { observer } from 'mobx-react';
-import { Document } from '../fields/Document';
-import { BasicField } from '../fields/BasicField';
-import { ListField } from '../fields/ListField';
-import { Key } from '../fields/Key';
-import { Opt, Field } from '../fields/Field';
-import { Server } from '../client/Server';
-
-configure({
- enforceActions: "observed"
-});
-
-@observer
-class FieldViewer extends React.Component<{ field: BasicField<any> }> {
- render() {
- return <span>{JSON.stringify(this.props.field.Data)} ({this.props.field.Id})</span>;
- }
-}
-
-@observer
-class KeyViewer extends React.Component<{ field: Key }> {
- render() {
- return this.props.field.Name;
- }
-}
-
-@observer
-class ListViewer extends React.Component<{ field: ListField<Field> }>{
- @observable
- expanded = false;
-
- render() {
- let content;
- if (this.expanded) {
- content = (
- <div>
- {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
- </div>
- )
- } else {
- content = <>[...] ({this.props.field.Id})</>
- }
- return (
- <div>
- <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
- {content}
- </div >
- )
- }
-}
-
-@observer
-class DocumentViewer extends React.Component<{ field: Document }> {
- private keyMap: ObservableMap<string, Key> = new ObservableMap
-
- private disposer?: Lambda;
-
- componentDidMount() {
- let f = () => {
- Array.from(this.props.field._proxies.keys()).forEach(id => {
- if (!this.keyMap.has(id)) {
- Server.GetField(id, (field) => {
- if (field && field instanceof Key) {
- this.keyMap.set(id, field);
- }
- })
- }
- });
- }
- this.disposer = this.props.field._proxies.observe(f)
- f()
- }
-
- componentWillUnmount() {
- if (this.disposer) {
- this.disposer();
- }
- }
-
- render() {
- let fields = Array.from(this.props.field._proxies.entries()).map(kv => {
- let key = this.keyMap.get(kv[0]);
- return (
- <div key={kv[0]}>
- <b>({key ? key.Name : kv[0]}): </b>
- <DebugViewer fieldId={kv[1]!}></DebugViewer>
- </div>
- )
- })
- return (
- <div>
- Document ({this.props.field.Id})
- <div style={{ paddingLeft: "25px" }}>
- {fields}
- </div>
- </div>
- )
- }
-}
-
-@observer
-class DebugViewer extends React.Component<{ fieldId: string }> {
- @observable
- private field?: Field;
-
- @observable
- private error?: string;
-
- constructor(props: { fieldId: string }) {
- super(props)
- this.update()
- }
-
- update() {
- Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
- this.field = field;
- if (!field) {
- this.error = `Field with id ${this.props.fieldId} not found`
- }
- }));
-
- }
-
- render() {
- let content;
- if (this.field) {
- // content = this.field.ToJson();
- if (this.field instanceof ListField) {
- content = (<ListViewer field={this.field} />)
- } else if (this.field instanceof Document) {
- content = (<DocumentViewer field={this.field} />)
- } else if (this.field instanceof BasicField) {
- content = (<FieldViewer field={this.field} />)
- } else if (this.field instanceof Key) {
- content = (<KeyViewer field={this.field} />)
- }
- } else if (this.error) {
- content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>
- } else {
- content = <>Field loading</>
- }
- return content;
- }
-}
-
-@observer
-class Viewer extends React.Component {
- @observable
- private idToAdd: string = '';
-
- @observable
- private ids: string[] = [];
-
- @action
- inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.idToAdd = e.target.value;
- }
-
- @action
- onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
- if (e.key === "Enter") {
- this.ids.push(this.idToAdd)
- this.idToAdd = ""
- }
- }
-
- render() {
- return (
- <>
- <input value={this.idToAdd}
- onChange={this.inputOnChange}
- onKeyDown={this.onKeyPress} />
- <div>
- {this.ids.map(id => {
- return <DebugViewer fieldId={id} key={id}></DebugViewer>
- })}
- </div>
- </>
- )
- }
-}
-
-ReactDOM.render((
- <div style={{ position: "absolute", width: "100%", height: "100%" }}>
- <Viewer />
- </div>),
- document.getElementById('root')
-); \ No newline at end of file
+
+// configure({
+// enforceActions: "observed"
+// });
+
+// @observer
+// class FieldViewer extends React.Component<{ field: BasicField<any> }> {
+// render() {
+// return <span>{JSON.stringify(this.props.field.Data)} ({this.props.field.Id})</span>;
+// }
+// }
+
+// @observer
+// class KeyViewer extends React.Component<{ field: Key }> {
+// render() {
+// return this.props.field.Name;
+// }
+// }
+
+// @observer
+// class ListViewer extends React.Component<{ field: ListField<Field> }>{
+// @observable
+// expanded = false;
+
+// render() {
+// let content;
+// if (this.expanded) {
+// content = (
+// <div>
+// {this.props.field.Data.map(field => <DebugViewer fieldId={field.Id} key={field.Id} />)}
+// </div>
+// );
+// } else {
+// content = <>[...] ({this.props.field.Id})</>;
+// }
+// return (
+// <div>
+// <button onClick={action(() => this.expanded = !this.expanded)}>Toggle</button>
+// {content}
+// </div >
+// );
+// }
+// }
+
+// @observer
+// class DocumentViewer extends React.Component<{ field: Document }> {
+// private keyMap: ObservableMap<string, Key> = new ObservableMap;
+
+// private disposer?: Lambda;
+
+// componentDidMount() {
+// let f = () => {
+// Array.from(this.props.field._proxies.keys()).forEach(id => {
+// if (!this.keyMap.has(id)) {
+// Server.GetField(id, (field) => {
+// if (field && field instanceof Key) {
+// this.keyMap.set(id, field);
+// }
+// });
+// }
+// });
+// };
+// this.disposer = this.props.field._proxies.observe(f);
+// f();
+// }
+
+// componentWillUnmount() {
+// if (this.disposer) {
+// this.disposer();
+// }
+// }
+
+// render() {
+// let fields = Array.from(this.props.field._proxies.entries()).map(kv => {
+// let key = this.keyMap.get(kv[0]);
+// return (
+// <div key={kv[0]}>
+// <b>({key ? key.Name : kv[0]}): </b>
+// <DebugViewer fieldId={kv[1]}></DebugViewer>
+// </div>
+// );
+// });
+// return (
+// <div>
+// Document ({this.props.field.Id})
+// <div style={{ paddingLeft: "25px" }}>
+// {fields}
+// </div>
+// </div>
+// );
+// }
+// }
+
+// @observer
+// class DebugViewer extends React.Component<{ fieldId: string }> {
+// @observable
+// private field?: Field;
+
+// @observable
+// private error?: string;
+
+// constructor(props: { fieldId: string }) {
+// super(props);
+// this.update();
+// }
+
+// update() {
+// Server.GetField(this.props.fieldId, action((field: Opt<Field>) => {
+// this.field = field;
+// if (!field) {
+// this.error = `Field with id ${this.props.fieldId} not found`;
+// }
+// }));
+
+// }
+
+// render() {
+// let content;
+// if (this.field) {
+// // content = this.field.ToJson();
+// if (this.field instanceof ListField) {
+// content = (<ListViewer field={this.field} />);
+// } else if (this.field instanceof Document) {
+// content = (<DocumentViewer field={this.field} />);
+// } else if (this.field instanceof BasicField) {
+// content = (<FieldViewer field={this.field} />);
+// } else if (this.field instanceof Key) {
+// content = (<KeyViewer field={this.field} />);
+// } else {
+// content = (<span>Unrecognized field type</span>);
+// }
+// } else if (this.error) {
+// content = <span>Field <b>{this.props.fieldId}</b> not found <button onClick={() => this.update()}>Refresh</button></span>;
+// } else {
+// content = <span>Field loading: {this.props.fieldId}</span>;
+// }
+// return content;
+// }
+// }
+
+// @observer
+// class Viewer extends React.Component {
+// @observable
+// private idToAdd: string = '';
+
+// @observable
+// private ids: string[] = [];
+
+// @action
+// inputOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+// this.idToAdd = e.target.value;
+// }
+
+// @action
+// onKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
+// if (e.key === "Enter") {
+// this.ids.push(this.idToAdd);
+// this.idToAdd = "";
+// }
+// }
+
+// render() {
+// return (
+// <>
+// <input value={this.idToAdd}
+// onChange={this.inputOnChange}
+// onKeyDown={this.onKeyPress} />
+// <div>
+// {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)}
+// </div>
+// </>
+// );
+// }
+// }
+
+// ReactDOM.render((
+// <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+// <Viewer />
+// </div>),
+// document.getElementById('root')
+// ); \ No newline at end of file
diff --git a/src/fields/AudioField.ts b/src/fields/AudioField.ts
deleted file mode 100644
index aefcc15c1..000000000
--- a/src/fields/AudioField.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class AudioField extends BasicField<URL> {
- constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("http://techslides.com/demos/samples/sample.mp3") : data, save, id);
- }
-
- toString(): string {
- return this.Data.href;
- }
-
-
- ToScriptString(): string {
- return `new AudioField("${this.Data}")`;
- }
-
- Copy(): Field {
- return new AudioField(this.Data);
- }
-
- ToJson(): { type: Types, data: URL, _id: string } {
- return {
- type: Types.Audio,
- data: this.Data,
- _id: this.Id
- }
- }
-
-} \ No newline at end of file
diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts
deleted file mode 100644
index a92c4a236..000000000
--- a/src/fields/BasicField.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Field, FieldId } from "./Field"
-import { observable, computed, action } from "mobx";
-import { Server } from "../client/Server";
-import { UndoManager } from "../client/util/UndoManager";
-
-export abstract class BasicField<T> extends Field {
- constructor(data: T, save: boolean, id?: FieldId) {
- super(id);
-
- this.data = data;
- if (save) {
- Server.UpdateField(this)
- }
- }
-
- UpdateFromServer(data: any) {
- if (this.data !== data) {
- this.data = data;
- }
- }
-
- @observable
- protected data: T;
-
- @computed
- get Data(): T {
- return this.data;
- }
-
- set Data(value: T) {
- if (this.data === value) {
- return;
- }
- let oldValue = this.data;
- this.setData(value);
- UndoManager.AddEvent({
- undo: () => this.Data = oldValue,
- redo: () => this.Data = value
- })
- Server.UpdateField(this);
- }
-
- protected setData(value: T) {
- this.data = value;
- }
-
- @action
- TrySetValue(value: any): boolean {
- if (typeof value == typeof this.data) {
- this.Data = value;
- return true;
- }
- return false;
- }
-
- GetValue(): any {
- return this.Data;
- }
-}
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
deleted file mode 100644
index 25e239417..000000000
--- a/src/fields/Document.ts
+++ /dev/null
@@ -1,306 +0,0 @@
-import { Key } from "./Key"
-import { KeyStore } from "./KeyStore";
-import { Field, Cast, FieldWaiting, FieldValue, FieldId, Opt } from "./Field"
-import { NumberField } from "./NumberField";
-import { ObservableMap, computed, action } from "mobx";
-import { TextField } from "./TextField";
-import { ListField } from "./ListField";
-import { Server } from "../client/Server";
-import { Types } from "../server/Message";
-import { UndoManager } from "../client/util/UndoManager";
-import { HtmlField } from "./HtmlField";
-
-export class Document extends Field {
- public fields: ObservableMap<string, { key: Key, field: Field }> = new ObservableMap();
- public _proxies: ObservableMap<string, FieldId> = new ObservableMap();
-
- constructor(id?: string, save: boolean = true) {
- super(id)
-
- if (save) {
- Server.UpdateField(this)
- }
- }
-
- UpdateFromServer(data: [string, string][]) {
- for (const key in data) {
- const element = data[key];
- this._proxies.set(element[0], element[1]);
- }
- }
-
- public Width = () => { return this.GetNumber(KeyStore.Width, 0) }
- public Height = () => { return this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0) * this.GetNumber(KeyStore.Width, 0) : 0) }
- public Scale = () => { return this.GetNumber(KeyStore.Scale, 1) }
-
- @computed
- public get Title() {
- return this.GetText(KeyStore.Title, "<untitled>");
- }
-
- /**
- * Get the field in the document associated with the given key. If the
- * associated field has not yet been filled in from the server, a request
- * to the server will automatically be sent, the value will be filled in
- * when the request is completed, and {@link Field.ts#FieldWaiting} will be returned.
- * @param key - The key of the value to get
- * @param ignoreProto - If true, ignore any prototype this document
- * might have and only search for the value on this immediate document.
- * If false (default), search up the prototype chain, starting at this document,
- * for a document that has a field associated with the given key, and return the first
- * one found.
- *
- * @returns If the document does not have a field associated with the given key, returns `undefined`.
- * If the document does have an associated field, but the field has not been fetched from the server, returns {@link Field.ts#FieldWaiting}.
- * If the document does have an associated field, and the field has not been fetched from the server, returns the associated field.
- */
- Get(key: Key, ignoreProto: boolean = false): FieldValue<Field> {
- let field: FieldValue<Field>;
- if (ignoreProto) {
- if (this.fields.has(key.Id)) {
- field = this.fields.get(key.Id)!.field;
- } else if (this._proxies.has(key.Id)) {
- Server.GetDocumentField(this, key);
- /*
- The field might have been instantly filled from the cache
- Maybe we want to just switch back to returning the value
- from Server.GetDocumentField if it's in the cache
- */
- if (this.fields.has(key.Id)) {
- field = this.fields.get(key.Id)!.field;
- } else {
- field = FieldWaiting;
- }
- }
- } else {
- let doc: FieldValue<Document> = this;
- while (doc && doc != FieldWaiting && field != FieldWaiting) {
- let curField = doc.fields.get(key.Id);
- let curProxy = doc._proxies.get(key.Id);
- if (!curField || (curProxy && curField.field.Id !== curProxy)) {
- if (curProxy) {
- Server.GetDocumentField(doc, key);
- /*
- The field might have been instantly filled from the cache
- Maybe we want to just switch back to returning the value
- from Server.GetDocumentField if it's in the cache
- */
- if (this.fields.has(key.Id)) {
- field = this.fields.get(key.Id)!.field;
- } else {
- field = FieldWaiting;
- }
- break;
- }
- if ((doc.fields.has(KeyStore.Prototype.Id) || doc._proxies.has(KeyStore.Prototype.Id))) {
- doc = doc.GetPrototype();
- } else {
- break;
- }
- } else {
- field = curField.field;
- break;
- }
- }
- if (doc == FieldWaiting)
- field = FieldWaiting;
- }
-
- return field;
- }
-
- /**
- * Tries to get the field associated with the given key, and if there is an
- * associated field, calls the given callback with that field.
- * @param key - The key of the value to get
- * @param callback - A function that will be called with the associated field, if it exists,
- * once it is fetched from the server (this may be immediately if the field has already been fetched).
- * Note: The callback will not be called if there is no associated field.
- * @returns `true` if the field exists on the document and `callback` will be called, and `false` otherwise
- */
- GetAsync(key: Key, callback: (field: Field) => void): boolean {
- //TODO: This should probably check if this.fields contains the key before calling Server.GetDocumentField
- //This currently doesn't deal with prototypes
- if (this._proxies.has(key.Id)) {
- Server.GetDocumentField(this, key, callback);
- return true;
- }
- return false;
- }
-
- GetTAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: Opt<T>) => void): boolean {
- return this.GetAsync(key, (field) => {
- callback(Cast(field, ctor));
- })
- }
-
- /**
- * Same as {@link Document#GetAsync}, except a field of the given type
- * will be created if there is no field associated with the given key,
- * or the field associated with the given key is not of the given type.
- * @param ctor - Constructor of the field type to get. E.g., TextField, ImageField, etc.
- */
- GetOrCreateAsync<T extends Field>(key: Key, ctor: { new(): T }, callback: (field: T) => void): void {
- //This currently doesn't deal with prototypes
- if (this._proxies.has(key.Id)) {
- Server.GetDocumentField(this, key, (field) => {
- if (field && field instanceof ctor) {
- callback(field);
- } else {
- let newField = new ctor();
- this.Set(key, newField);
- callback(newField);
- }
- });
- } else {
- let newField = new ctor();
- this.Set(key, newField);
- callback(newField);
- }
- }
-
- /**
- * Same as {@link Document#Get}, except that it will additionally
- * check if the field is of the given type.
- * @param ctor - Constructor of the field type to get. E.g., `TextField`, `ImageField`, etc.
- * @returns Same as {@link Document#Get}, except will return `undefined`
- * if there is an associated field but it is of the wrong type.
- */
- GetT<T extends Field = Field>(key: Key, ctor: { new(...args: any[]): T }, ignoreProto: boolean = false): FieldValue<T> {
- var getfield = this.Get(key, ignoreProto);
- if (getfield != FieldWaiting) {
- return Cast(getfield, ctor);
- }
- return FieldWaiting;
- }
-
- GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }, ignoreProto: boolean = false): T {
- const field = this.GetT(key, ctor, ignoreProto);
- if (field && field != FieldWaiting) {
- return field;
- }
- const newField = new ctor();
- this.Set(key, newField);
- return newField;
- }
-
- GetData<T, U extends Field & { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T {
- let val = this.Get(key);
- let vval = (val && val instanceof ctor) ? val.Data : defaultVal;
- return vval;
- }
-
- GetHtml(key: Key, defaultVal: string): string {
- return this.GetData(key, HtmlField, defaultVal);
- }
-
- GetNumber(key: Key, defaultVal: number): number {
- return this.GetData(key, NumberField, defaultVal);
- }
-
- GetText(key: Key, defaultVal: string): string {
- return this.GetData(key, TextField, defaultVal);
- }
-
- GetList<T extends Field>(key: Key, defaultVal: T[]): T[] {
- return this.GetData<T[], ListField<T>>(key, ListField, defaultVal)
- }
-
- @action
- Set(key: Key, field: Field | undefined): void {
- let old = this.fields.get(key.Id);
- let oldField = old ? old.field : undefined;
- if (field) {
- this.fields.set(key.Id, { key, field });
- this._proxies.set(key.Id, field.Id)
- // Server.AddDocumentField(this, key, field);
- } else {
- this.fields.delete(key.Id);
- this._proxies.delete(key.Id)
- // Server.DeleteDocumentField(this, key);
- }
- if (oldField || field) {
- UndoManager.AddEvent({
- undo: () => this.Set(key, oldField),
- redo: () => this.Set(key, field)
- })
- }
- Server.UpdateField(this);
- }
-
- @action
- SetData<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }, replaceWrongType = true) {
-
- let field = this.Get(key, true);
- if (field instanceof ctor) {
- field.Data = value;
- } else if (!field || replaceWrongType) {
- let newField = new ctor();
- newField.Data = value;
- this.Set(key, newField);
- }
- }
-
- @action
- SetText(key: Key, value: string, replaceWrongType = true) {
- this.SetData(key, value, TextField, replaceWrongType);
- }
-
- @action
- SetNumber(key: Key, value: number, replaceWrongType = true) {
- this.SetData(key, value, NumberField, replaceWrongType);
- }
-
- GetPrototype(): FieldValue<Document> {
- return this.GetT(KeyStore.Prototype, Document, true);
- }
-
- GetAllPrototypes(): Document[] {
- let protos: Document[] = [];
- let doc: FieldValue<Document> = this;
- while (doc && doc != FieldWaiting) {
- protos.push(doc);
- doc = doc.GetPrototype();
- }
- return protos;
- }
-
- MakeDelegate(id?: string): Document {
- let delegate = new Document(id);
-
- delegate.Set(KeyStore.Prototype, this);
-
- return delegate;
- }
-
- ToScriptString(): string {
- return "";
- }
-
- TrySetValue(value: any): boolean {
- throw new Error("Method not implemented.");
- }
- GetValue() {
- var title = (this._proxies.has(KeyStore.Title.Id) ? "???" : this.Title) + "(" + this.Id + ")";
- return title;
- //throw new Error("Method not implemented.");
- }
- Copy(): Field {
- throw new Error("Method not implemented.");
- }
-
- ToJson(): { type: Types, data: [string, string][], _id: string } {
- let fields: [string, string][] = []
- this._proxies.forEach((field, key) => {
- if (field) {
- fields.push([key, field as string])
- }
- });
-
- return {
- type: Types.Document,
- data: fields,
- _id: this.Id
- }
- }
-} \ No newline at end of file
diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts
deleted file mode 100644
index 9d3c209b4..000000000
--- a/src/fields/DocumentReference.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Field, Opt, FieldValue, FieldId } from "./Field";
-import { Document } from "./Document";
-import { Key } from "./Key";
-import { Types } from "../server/Message";
-import { ObjectID } from "bson";
-
-export class DocumentReference extends Field {
- get Key(): Key {
- return this.key;
- }
-
- get Document(): Document {
- return this.document;
- }
-
- constructor(private document: Document, private key: Key) {
- super();
- }
-
- UpdateFromServer() {
-
- }
-
- Dereference(): FieldValue<Field> {
- return this.document.Get(this.key);
- }
-
- DereferenceToRoot(): FieldValue<Field> {
- let field: FieldValue<Field> = this;
- while (field instanceof DocumentReference) {
- field = field.Dereference();
- }
- return field;
- }
-
- TrySetValue(value: any): boolean {
- throw new Error("Method not implemented.");
- }
- GetValue() {
- throw new Error("Method not implemented.");
- }
- Copy(): Field {
- throw new Error("Method not implemented.");
- }
-
- ToScriptString(): string {
- return "";
- }
-
- ToJson(): { type: Types, data: FieldId, _id: string } {
- return {
- type: Types.DocumentReference,
- data: this.document.Id,
- _id: this.Id
- }
- }
-} \ No newline at end of file
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
deleted file mode 100644
index d48509a47..000000000
--- a/src/fields/Field.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-
-import { Utils } from "../Utils";
-import { Types } from "../server/Message";
-import { computed } from "mobx";
-
-export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T }): Opt<T> {
- if (field) {
- if (ctor && field instanceof ctor) {
- return field;
- }
- }
- return undefined;
-}
-
-export const FieldWaiting: FIELD_WAITING = "<Waiting>";
-export type FIELD_WAITING = "<Waiting>";
-export type FieldId = string;
-export type Opt<T> = T | undefined;
-export type FieldValue<T> = Opt<T> | FIELD_WAITING;
-
-export abstract class Field {
- //FieldUpdated: TypedEvent<Opt<FieldUpdatedArgs>> = new TypedEvent<Opt<FieldUpdatedArgs>>();
-
- init(callback: (res: Field) => any) {
- callback(this);
- }
-
- private id: FieldId;
-
- @computed
- get Id(): FieldId {
- return this.id;
- }
-
- constructor(id: Opt<FieldId> = undefined) {
- this.id = id || Utils.GenerateGuid();
- }
-
- Dereference(): FieldValue<Field> {
- return this;
- }
- DereferenceToRoot(): FieldValue<Field> {
- return this;
- }
-
- DereferenceT<T extends Field = Field>(ctor: { new(): T }): FieldValue<T> {
- return Cast(this.Dereference(), ctor);
- }
-
- DereferenceToRootT<T extends Field = Field>(ctor: { new(): T }): FieldValue<T> {
- return Cast(this.DereferenceToRoot(), ctor);
- }
-
- Equals(other: Field): boolean {
- return this.id === other.id;
- }
-
- abstract UpdateFromServer(serverData: any): void;
-
- abstract ToScriptString(): string;
-
- abstract TrySetValue(value: any): boolean;
-
- abstract GetValue(): any;
-
- abstract Copy(): Field;
-
- abstract ToJson(): { _id: string, type: Types, data: any }
-} \ No newline at end of file
diff --git a/src/fields/FieldUpdatedArgs.ts b/src/fields/FieldUpdatedArgs.ts
deleted file mode 100644
index 23ccf2a5a..000000000
--- a/src/fields/FieldUpdatedArgs.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Field, Opt } from "./Field";
-import { Document } from "./Document";
-import { Key } from "./Key";
-
-export enum FieldUpdatedAction {
- Add,
- Remove,
- Replace,
- Update
-}
-
-export interface FieldUpdatedArgs {
- field: Field;
- action: FieldUpdatedAction;
-}
-
-export interface DocumentUpdatedArgs {
- field: Document;
- key: Key;
-
- oldValue: Opt<Field>;
- newValue: Opt<Field>;
-
- fieldArgs?: FieldUpdatedArgs;
-
- action: FieldUpdatedAction;
-}
diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts
deleted file mode 100644
index a07326095..000000000
--- a/src/fields/HtmlField.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Types } from "../server/Message";
-import { FieldId } from "./Field";
-
-export class HtmlField extends BasicField<string> {
- constructor(data: string = "<html></html>", id?: FieldId, save: boolean = true) {
- super(data, save, id);
- }
-
- ToScriptString(): string {
- return `new HtmlField("${this.Data}")`;
- }
-
- Copy() {
- return new HtmlField(this.Data);
- }
-
- ToJson(): { _id: string; type: Types; data: any; } {
- return {
- type: Types.Html,
- data: this.Data,
- _id: this.Id,
- }
- }
-} \ No newline at end of file
diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts
deleted file mode 100644
index be8d73e68..000000000
--- a/src/fields/ImageField.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class ImageField extends BasicField<URL> {
- constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
- }
-
- toString(): string {
- return this.Data.href;
- }
-
- ToScriptString(): string {
- return `new ImageField("${this.Data}")`;
- }
-
- Copy(): Field {
- return new ImageField(this.Data);
- }
-
- ToJson(): { type: Types, data: URL, _id: string } {
- return {
- type: Types.Image,
- data: this.Data,
- _id: this.Id
- }
- }
-} \ No newline at end of file
diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts
deleted file mode 100644
index 2a4ed18e7..000000000
--- a/src/fields/InkField.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Types } from "../server/Message";
-import { FieldId } from "./Field";
-import { observable, ObservableMap } from "mobx";
-
-export enum InkTool {
- None,
- Pen,
- Highlighter,
- Eraser
-}
-export interface StrokeData {
- pathData: Array<{ x: number, y: number }>;
- color: string;
- width: string;
- tool: InkTool;
- page: number;
-}
-export type StrokeMap = Map<string, StrokeData>;
-
-export class InkField extends BasicField<StrokeMap> {
- constructor(data: StrokeMap = new Map, id?: FieldId, save: boolean = true) {
- super(data, save, id);
- }
-
- ToScriptString(): string {
- return `new InkField("${this.Data}")`;
- }
-
- Copy() {
- return new InkField(this.Data);
- }
-
- ToJson(): { _id: string; type: Types; data: any; } {
- return {
- type: Types.Ink,
- data: this.Data,
- _id: this.Id,
- }
- }
-
- UpdateFromServer(data: any) {
- this.data = new ObservableMap(data);
- }
-
- static FromJson(id: string, data: any): InkField {
- let map: StrokeMap = new Map<string, StrokeData>();
- Object.keys(data).forEach(key => {
- map.set(key, data[key]);
- });
- return new InkField(map, id, false);
- }
-} \ No newline at end of file
diff --git a/src/fields/KVPField.ts b/src/fields/KVPField.ts
deleted file mode 100644
index a7ecc0768..000000000
--- a/src/fields/KVPField.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BasicField } from "./BasicField"
-import { FieldId } from "./Field";
-import { Types } from "../server/Message";
-import { Document } from "./Document"
-
-export class KVPField extends BasicField<Document> {
- constructor(data: Document | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new Document() : data, save, id);
- }
-
- toString(): string {
- return this.Data.Title;
- }
-
- ToScriptString(): string {
- return `new KVPField("${this.Data}")`;
- }
-
- Copy() {
- return new KVPField(this.Data);
- }
-
- ToJson(): { type: Types, data: Document, _id: string } {
- return {
- type: Types.Text,
- data: this.Data,
- _id: this.Id
- }
- }
-} \ No newline at end of file
diff --git a/src/fields/Key.ts b/src/fields/Key.ts
deleted file mode 100644
index 00d78d516..000000000
--- a/src/fields/Key.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { Field, FieldId } from "./Field"
-import { Utils } from "../Utils";
-import { observable } from "mobx";
-import { Types } from "../server/Message";
-import { Server } from "../client/Server";
-
-export class Key extends Field {
- private name: string;
-
- get Name(): string {
- return this.name;
- }
-
- constructor(name: string, id?: string, save: boolean = true) {
- super(id || Utils.GenerateDeterministicGuid(name));
-
- this.name = name;
- if (save) {
- Server.UpdateField(this)
- }
- }
-
- UpdateFromServer(data: string) {
- this.name = data;
- }
-
- TrySetValue(value: any): boolean {
- throw new Error("Method not implemented.");
- }
-
- GetValue() {
- return this.Name;
- }
-
- Copy(): Field {
- return this;
- }
-
- ToScriptString(): string {
- return name;
- }
-
- ToJson(): { type: Types, data: string, _id: string } {
- return {
- type: Types.Key,
- data: this.name,
- _id: this.Id
- }
- }
-} \ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
deleted file mode 100644
index f93a68c85..000000000
--- a/src/fields/KeyStore.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Key } from "./Key";
-
-export namespace KeyStore {
- export const Prototype = new Key("Prototype");
- export const X = new Key("X");
- export const Y = new Key("Y");
- export const Page = new Key("Page");
- export const Title = new Key("Title");
- export const Author = new Key("Author");
- export const PanX = new Key("PanX");
- export const PanY = new Key("PanY");
- export const Scale = new Key("Scale");
- export const NativeWidth = new Key("NativeWidth");
- export const NativeHeight = new Key("NativeHeight");
- export const Width = new Key("Width");
- export const Height = new Key("Height");
- export const ZIndex = new Key("ZIndex");
- export const Data = new Key("Data");
- export const Annotations = new Key("Annotations");
- export const ViewType = new Key("ViewType");
- export const Layout = new Key("Layout");
- export const BackgroundLayout = new Key("BackgroundLayout");
- export const OverlayLayout = new Key("OverlayLayout");
- export const LayoutKeys = new Key("LayoutKeys");
- export const LayoutFields = new Key("LayoutFields");
- export const ColumnsKey = new Key("SchemaColumns");
- export const Caption = new Key("Caption");
- export const ActiveFrame = new Key("ActiveFrame");
- export const DocumentText = new Key("DocumentText");
- export const LinkedToDocs = new Key("LinkedToDocs");
- export const LinkedFromDocs = new Key("LinkedFromDocs");
- export const LinkDescription = new Key("LinkDescription");
- export const LinkTags = new Key("LinkTag");
- export const Thumbnail = new Key("Thumbnail");
- export const CurPage = new Key("CurPage");
- export const NumPages = new Key("NumPages");
- export const Ink = new Key("Ink");
-}
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
deleted file mode 100644
index ce32da0a6..000000000
--- a/src/fields/ListField.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { action, IArrayChange, IArraySplice, IObservableArray, observe, observable, Lambda } from "mobx";
-import { Server } from "../client/Server";
-import { UndoManager } from "../client/util/UndoManager";
-import { Types } from "../server/Message";
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-
-export class ListField<T extends Field> extends BasicField<T[]> {
- private _proxies: string[] = []
- constructor(data: T[] = [], id?: FieldId, save: boolean = true) {
- super(data, save, id);
- this.updateProxies();
- if (save) {
- Server.UpdateField(this);
- }
- this.observeList();
- }
-
- private observeDisposer: Lambda | undefined;
- private observeList(): void {
- this.observeDisposer = observe(this.Data as IObservableArray<T>, (change: IArrayChange<T> | IArraySplice<T>) => {
- this.updateProxies()
- if (change.type == "splice") {
- UndoManager.AddEvent({
- undo: () => this.Data.splice(change.index, change.addedCount, ...change.removed),
- redo: () => this.Data.splice(change.index, change.removedCount, ...change.added)
- })
- } else {
- UndoManager.AddEvent({
- undo: () => this.Data[change.index] = change.oldValue,
- redo: () => this.Data[change.index] = change.newValue
- })
- }
- Server.UpdateField(this);
- });
- }
-
- protected setData(value: T[]) {
- if (this.observeDisposer) {
- this.observeDisposer()
- }
- this.data = observable(value);
- this.updateProxies();
- this.observeList();
- }
-
- private updateProxies() {
- this._proxies = this.Data.map(field => field.Id);
- }
-
- UpdateFromServer(fields: string[]) {
- this._proxies = fields;
- }
- private arraysEqual(a: any[], b: any[]) {
- if (a === b) return true;
- if (a == null || b == null) return false;
- if (a.length != b.length) return false;
-
- // If you don't care about the order of the elements inside
- // the array, you should sort both arrays here.
- // Please note that calling sort on an array will modify that array.
- // you might want to clone your array first.
-
- for (var i = 0; i < a.length; ++i) {
- if (a[i] !== b[i]) return false;
- }
- return true;
- }
-
- init(callback: (field: Field) => any) {
- Server.GetFields(this._proxies, action((fields: { [index: string]: Field }) => {
- if (!this.arraysEqual(this._proxies, this.Data.map(field => field.Id))) {
- var dataids = this.data.map(d => d.Id);
- var added = this.data.length == this._proxies.length - 1;
- var deleted = this.data.length > this._proxies.length;
- for (let i = 0; i < dataids.length && added; i++)
- added = this._proxies.indexOf(dataids[i]) != -1;
- for (let i = 0; i < this._proxies.length && deleted; i++)
- deleted = dataids.indexOf(this._proxies[i]) != -1;
- if (added) { // if only 1 items was added
- for (let i = 0; i < this._proxies.length; i++)
- if (dataids.indexOf(this._proxies[i]) === -1)
- this.Data.splice(i, 0, fields[this._proxies[i]] as T);
- } else if (deleted) { // if only items were deleted
- for (let i = this.data.length - 1; i >= 0; i--) {
- if (this._proxies.indexOf(this.data[i].Id) === -1) {
- this.Data.splice(i, 1);
- }
- }
- } else // otherwise, just rebuild the whole list
- this.data = this._proxies.map(id => fields[id] as T)
- observe(this.Data, () => {
- this.updateProxies()
- Server.UpdateField(this);
- })
- }
- callback(this);
- }))
- }
-
- ToScriptString(): string {
- return "new ListField([" + this.Data.map(field => field.ToScriptString()).join(", ") + "])";
- }
-
- Copy(): Field {
- return new ListField<T>(this.Data);
- }
-
- ToJson(): { type: Types, data: string[], _id: string } {
- return {
- type: Types.List,
- data: this._proxies,
- _id: this.Id
- }
- }
-
- static FromJson(id: string, ids: string[]): ListField<Field> {
- let list = new ListField([], id, false);
- list._proxies = ids;
- return list
- }
-} \ No newline at end of file
diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts
deleted file mode 100644
index 47dfc74cb..000000000
--- a/src/fields/NumberField.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { BasicField } from "./BasicField"
-import { Types } from "../server/Message";
-import { FieldId } from "./Field";
-
-export class NumberField extends BasicField<number> {
- constructor(data: number = 0, id?: FieldId, save: boolean = true) {
- super(data, save, id);
- }
-
- ToScriptString(): string {
- return "new NumberField(this.Data)";
- }
-
- Copy() {
- return new NumberField(this.Data);
- }
-
- ToJson(): { _id: string, type: Types, data: number } {
- return {
- _id: this.Id,
- type: Types.Number,
- data: this.Data
- }
- }
-} \ No newline at end of file
diff --git a/src/fields/PDFField.ts b/src/fields/PDFField.ts
deleted file mode 100644
index f3a009001..000000000
--- a/src/fields/PDFField.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { observable } from "mobx"
-import { Types } from "../server/Message";
-
-
-
-export class PDFField extends BasicField<URL> {
- constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
- }
-
- toString(): string {
- return this.Data.href;
- }
-
- Copy(): Field {
- return new PDFField(this.Data);
- }
-
- ToScriptString(): string {
- return `new PDFField("${this.Data}")`;
- }
-
- ToJson(): { type: Types, data: URL, _id: string } {
- return {
- type: Types.PDF,
- data: this.Data,
- _id: this.Id
- }
- }
-
- @observable
- Page: Number = 1;
-
-} \ No newline at end of file
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
deleted file mode 100644
index 5efb43314..000000000
--- a/src/fields/RichTextField.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Types } from "../server/Message";
-import { FieldId } from "./Field";
-
-export class RichTextField extends BasicField<string> {
- constructor(data: string = "", id?: FieldId, save: boolean = true) {
- super(data, save, id);
- }
-
- ToScriptString(): string {
- return `new RichTextField(${this.Data})`;
- }
-
- Copy() {
- return new RichTextField(this.Data);
- }
-
- ToJson(): { type: Types, data: string, _id: string } {
- return {
- type: Types.RichText,
- data: this.Data,
- _id: this.Id
- }
- }
-
-} \ No newline at end of file
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
new file mode 100644
index 000000000..ae532c9e2
--- /dev/null
+++ b/src/fields/ScriptField.ts
@@ -0,0 +1,101 @@
+// import { Field, FieldId } from "./Field";
+// import { Types } from "../server/Message";
+// import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting";
+// import { Server } from "../client/Server";
+// import { Without } from "../Utils";
+
+// export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> {
+// capturedIds: { [id: string]: string };
+// }
+
+// export interface ScriptData {
+// script: string;
+// options: SerializableOptions;
+// }
+
+// export class ScriptField extends Field {
+// private _script?: CompiledScript;
+// get script(): CompiledScript {
+// return this._script!;
+// }
+// private options?: ScriptData;
+
+// constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) {
+// super(id);
+
+// this._script = script;
+
+// if (save) {
+// Server.UpdateField(this);
+// }
+// }
+
+// ToScriptString() {
+// return "new ScriptField(...)";
+// }
+
+// GetValue() {
+// return this.script;
+// }
+
+// TrySetValue(): boolean {
+// throw new Error("Script fields currently can't be modified");
+// }
+
+// UpdateFromServer() {
+// throw new Error("Script fields currently can't be updated");
+// }
+
+// static FromJson(id: string, data: ScriptData): ScriptField {
+// let field = new ScriptField(undefined, id, false);
+// field.options = data;
+// return field;
+// }
+
+// init(callback: (res: Field) => any) {
+// const options = this.options!;
+// const keys = Object.keys(options.options.capturedIds);
+// Server.GetFields(keys).then(fields => {
+// let captured: { [name: string]: Field } = {};
+// keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
+// const opts: ScriptOptions = {
+// addReturn: options.options.addReturn,
+// params: options.options.params,
+// requiredType: options.options.requiredType,
+// capturedVariables: captured
+// };
+// const script = CompileScript(options.script, opts);
+// if (!script.compiled) {
+// throw new Error("Can't compile script");
+// }
+// this._script = script;
+// callback(this);
+// });
+// }
+
+// ToJson() {
+// const { options, originalScript } = this.script;
+// let capturedIds: { [id: string]: string } = {};
+// for (const capt in options.capturedVariables) {
+// capturedIds[options.capturedVariables[capt].Id] = capt;
+// }
+// const opts: SerializableOptions = {
+// ...options,
+// capturedIds
+// };
+// delete (opts as any).capturedVariables;
+// return {
+// id: this.Id,
+// type: Types.Script,
+// data: {
+// script: originalScript,
+// options: opts,
+// },
+// };
+// }
+
+// Copy(): Field {
+// //Script fields are currently immutable, so we can fake copy them
+// return this;
+// }
+// } \ No newline at end of file
diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts
deleted file mode 100644
index 71d8ea310..000000000
--- a/src/fields/TextField.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { BasicField } from "./BasicField"
-import { FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class TextField extends BasicField<string> {
- constructor(data: string = "", id?: FieldId, save: boolean = true) {
- super(data, save, id);
- }
-
- ToScriptString(): string {
- return `new TextField("${this.Data}")`;
- }
-
- Copy() {
- return new TextField(this.Data);
- }
-
- ToJson(): { type: Types, data: string, _id: string } {
- return {
- type: Types.Text,
- data: this.Data,
- _id: this.Id
- }
- }
-} \ No newline at end of file
diff --git a/src/fields/VideoField.ts b/src/fields/VideoField.ts
deleted file mode 100644
index 5f4ae19bf..000000000
--- a/src/fields/VideoField.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class VideoField extends BasicField<URL> {
- constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("http://techslides.com/demos/sample-videos/small.mp4") : data, save, id);
- }
-
- toString(): string {
- return this.Data.href;
- }
-
- ToScriptString(): string {
- return `new VideoField("${this.Data}")`;
- }
-
- Copy(): Field {
- return new VideoField(this.Data);
- }
-
- ToJson(): { type: Types, data: URL, _id: string } {
- return {
- type: Types.Video,
- data: this.Data,
- _id: this.Id
- }
- }
-
-} \ No newline at end of file
diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts
deleted file mode 100644
index 8f945d686..000000000
--- a/src/fields/WebField.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BasicField } from "./BasicField";
-import { Field, FieldId } from "./Field";
-import { Types } from "../server/Message";
-
-export class WebField extends BasicField<URL> {
- constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
- super(data == undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id);
- }
-
- toString(): string {
- return this.Data.href;
- }
-
- ToScriptString(): string {
- return `new WebField("${this.Data}")`;
- }
-
- Copy(): Field {
- return new WebField(this.Data);
- }
-
- ToJson(): { type: Types, data: URL, _id: string } {
- return {
- type: Types.Web,
- data: this.Data,
- _id: this.Id
- }
- }
-
-} \ No newline at end of file
diff --git a/src/mobile/ImageUpload.scss b/src/mobile/ImageUpload.scss
new file mode 100644
index 000000000..d0b7d4e41
--- /dev/null
+++ b/src/mobile/ImageUpload.scss
@@ -0,0 +1,13 @@
+.imgupload_cont {
+ height: 100vh;
+ width: 100vw;
+ align-content: center;
+ .button_file {
+ text-align: center;
+ height: 50%;
+ width: 50%;
+ background-color: paleturquoise;
+ color: grey;
+ font-size: 3em;
+ }
+} \ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
new file mode 100644
index 000000000..1f9e160ce
--- /dev/null
+++ b/src/mobile/ImageUpload.tsx
@@ -0,0 +1,76 @@
+import * as ReactDOM from 'react-dom';
+import * as rp from 'request-promise';
+import { Docs } from '../client/documents/Documents';
+import { RouteStore } from '../server/RouteStore';
+import "./ImageUpload.scss";
+import React = require('react');
+import { DocServer } from '../client/DocServer';
+import { Opt, Doc } from '../new_fields/Doc';
+import { Cast } from '../new_fields/Types';
+import { listSpec } from '../new_fields/Schema';
+import { List } from '../new_fields/List';
+
+
+
+
+// const onPointerDown = (e: React.TouchEvent) => {
+// let imgInput = document.getElementById("input_image_file");
+// if (imgInput) {
+// imgInput.click();
+// }
+// }
+
+const onFileLoad = async (file: any) => {
+ let imgPrev = document.getElementById("img_preview");
+ if (imgPrev) {
+ let files: File[] = file.target.files;
+ if (files.length !== 0) {
+ console.log(files[0]);
+ let formData = new FormData();
+ formData.append("file", files[0]);
+
+ const upload = window.location.origin + "/upload";
+ const res = await fetch(upload, {
+ method: 'POST',
+ body: formData
+ });
+ const json = await res.json();
+ json.map(async (file: any) => {
+ let path = window.location.origin + file;
+ var doc = Docs.ImageDocument(path, { nativeWidth: 200, width: 200 });
+
+ const res = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId));
+ if (!res) {
+ throw new Error("No user id returned");
+ }
+ const field = await DocServer.GetRefField(res);
+ let pending: Opt<Doc>;
+ if (field instanceof Doc) {
+ pending = await Cast(field.optionalRightCollection, Doc);
+ }
+ if (pending) {
+ const data = await Cast(pending.data, listSpec(Doc));
+ if (data) {
+ data.push(doc);
+ } else {
+ pending.data = new List([doc]);
+ }
+ }
+ });
+
+ // console.log(window.location.origin + file[0])
+
+ //imgPrev.setAttribute("src", window.location.origin + files[0].name)
+ }
+ }
+};
+
+ReactDOM.render((
+ <div className="imgupload_cont">
+ {/* <button className = "button_file" = {onPointerDown}> Open Image </button> */}
+ <input type="file" accept="image/*" onChange={onFileLoad} className="input_file" id="input_image_file"></input>
+ <img id="img_preview" src=""></img>
+ <div id="message" />
+ </div>),
+ document.getElementById('root')
+); \ No newline at end of file
diff --git a/src/fields/KVPField b/src/mobile/InkControls.tsx
index e69de29bb..e69de29bb 100644
--- a/src/fields/KVPField
+++ b/src/mobile/InkControls.tsx
diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts
new file mode 100644
index 000000000..fc144222c
--- /dev/null
+++ b/src/new_fields/CursorField.ts
@@ -0,0 +1,55 @@
+import { ObjectField, Copy, OnUpdate } from "./ObjectField";
+import { observable } from "mobx";
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, createSimpleSchema, object } from "serializr";
+
+export type CursorPosition = {
+ x: number,
+ y: number
+};
+
+export type CursorMetadata = {
+ id: string,
+ identifier: string
+};
+
+export type CursorData = {
+ metadata: CursorMetadata,
+ position: CursorPosition
+};
+
+const PositionSchema = createSimpleSchema({
+ x: true,
+ y: true
+});
+
+const MetadataSchema = createSimpleSchema({
+ id: true,
+ identifier: true
+});
+
+const CursorSchema = createSimpleSchema({
+ metadata: object(MetadataSchema),
+ position: object(PositionSchema)
+});
+
+@Deserializable("cursor")
+export default class CursorField extends ObjectField {
+
+ @serializable(object(CursorSchema))
+ readonly data: CursorData;
+
+ constructor(data: CursorData) {
+ super();
+ this.data = data;
+ }
+
+ setPosition(position: CursorPosition) {
+ this.data.position = position;
+ this[OnUpdate]();
+ }
+
+ [Copy]() {
+ return new CursorField(this.data);
+ }
+} \ No newline at end of file
diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts
new file mode 100644
index 000000000..c0a79f267
--- /dev/null
+++ b/src/new_fields/DateField.ts
@@ -0,0 +1,18 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, date } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+
+@Deserializable("date")
+export class DateField extends ObjectField {
+ @serializable(date())
+ readonly date: Date;
+
+ constructor(date: Date = new Date()) {
+ super();
+ this.date = date;
+ }
+
+ [Copy]() {
+ return new DateField(this.date);
+ }
+}
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
new file mode 100644
index 000000000..ad3b880cd
--- /dev/null
+++ b/src/new_fields/Doc.ts
@@ -0,0 +1,257 @@
+import { observable, action } from "mobx";
+import { serializable, primitive, map, alias, list } from "serializr";
+import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
+import { DocServer } from "../client/DocServer";
+import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
+import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
+import { UndoManager, undoBatch } from "../client/util/UndoManager";
+import { listSpec } from "./Schema";
+import { List } from "./List";
+import { ObjectField, Parent, OnUpdate } from "./ObjectField";
+import { RefField, FieldId, Id, HandleUpdate } from "./RefField";
+import { Docs } from "../client/documents/Documents";
+
+export function IsField(field: any): field is Field {
+ return (typeof field === "string")
+ || (typeof field === "number")
+ || (typeof field === "boolean")
+ || (field instanceof ObjectField)
+ || (field instanceof RefField);
+}
+export type Field = number | string | boolean | ObjectField | RefField;
+export type Opt<T> = T | undefined;
+export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>;
+export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>;
+
+export const Update = Symbol("Update");
+export const Self = Symbol("Self");
+export const SelfProxy = Symbol("SelfProxy");
+export const WidthSym = Symbol("Width");
+export const HeightSym = Symbol("Height");
+
+/**
+ * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs.
+ * If a default value is given, that will be returned instead of undefined.
+ * If a default value is given, the returned value should not be modified as it might be a temporary value.
+ * If no default value is given, and the returned value is not undefined, it can be safely modified.
+ */
+export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>;
+export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>;
+export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
+ const list = Cast(field, listSpec(Doc));
+ return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
+}
+
+export function DocListCast(field: FieldResult): Doc[] {
+ return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc) as Doc[];
+}
+
+@Deserializable("doc").withFields(["id"])
+export class Doc extends RefField {
+ constructor(id?: FieldId, forceSave?: boolean) {
+ super(id);
+ const doc = new Proxy<this>(this, {
+ set: setter,
+ get: getter,
+ has: (target, key) => key in target.__fields,
+ ownKeys: target => Object.keys(target.__fields),
+ getOwnPropertyDescriptor: (target, prop) => {
+ if (prop in target.__fields) {
+ return {
+ configurable: true,//TODO Should configurable be true?
+ enumerable: true,
+ };
+ }
+ return Reflect.getOwnPropertyDescriptor(target, prop);
+ },
+ deleteProperty: deleteProperty,
+ defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+ });
+ this[SelfProxy] = doc;
+ if (!id || forceSave) {
+ DocServer.CreateField(doc);
+ }
+ return doc;
+ }
+
+ proto: Opt<Doc>;
+ [key: string]: FieldResult;
+
+ @serializable(alias("fields", map(autoObject())))
+ private get __fields() {
+ return this.___fields;
+ }
+
+ private set __fields(value) {
+ this.___fields = value;
+ for (const key in value) {
+ const field = value[key];
+ if (!(field instanceof ObjectField)) continue;
+ field[Parent] = this[Self];
+ field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ }
+ }
+
+ @observable
+ //{ [key: string]: Field | FieldWaiting | undefined }
+ private ___fields: any = {};
+
+ private [Update] = (diff: any) => {
+ DocServer.UpdateField(this[Id], diff);
+ }
+
+ private [Self] = this;
+ private [SelfProxy]: any;
+ public [WidthSym] = () => NumCast(this[SelfProxy].width); // bcz: is this the right way to access width/height? it didn't work with : this.width
+ public [HeightSym] = () => NumCast(this[SelfProxy].height);
+
+ public [HandleUpdate](diff: any) {
+ console.log(diff);
+ const set = diff.$set;
+ if (set) {
+ for (const key in set) {
+ if (!key.startsWith("fields.")) {
+ continue;
+ }
+ const value = SerializationHelper.Deserialize(set[key]);
+ const fKey = key.substring(7);
+ this[fKey] = value;
+ }
+ }
+ }
+}
+
+export namespace Doc {
+ // export function GetAsync(doc: Doc, key: string, ignoreProto: boolean = false): Promise<Field | undefined> {
+ // const self = doc[Self];
+ // return new Promise(res => getField(self, key, ignoreProto, res));
+ // }
+ // export function GetTAsync<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): Promise<T | undefined> {
+ // return new Promise(async res => {
+ // const field = await GetAsync(doc, key, ignoreProto);
+ // return Cast(field, ctor);
+ // });
+ // }
+ export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
+ const self = doc[Self];
+ return getField(self, key, ignoreProto);
+ }
+ export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
+ return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
+ }
+ export async function SetOnPrototype(doc: Doc, key: string, value: Field) {
+ const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc;
+
+ if (proto) {
+ proto[key] = value;
+ }
+ }
+ export function GetAllPrototypes(doc: Doc): Doc[] {
+ const protos: Doc[] = [];
+ let d: Opt<Doc> = doc;
+ while (d) {
+ protos.push(d);
+ d = FieldValue(d.proto);
+ }
+ return protos;
+ }
+ export function assign<K extends string>(doc: Doc, fields: Partial<Record<K, Opt<Field>>>) {
+ for (const key in fields) {
+ if (fields.hasOwnProperty(key)) {
+ const value = fields[key];
+ // Do we want to filter out undefineds?
+ // if (value !== undefined) {
+ doc[key] = value;
+ // }
+ }
+ }
+ return doc;
+ }
+
+ // compare whether documents or their protos match
+ export function AreProtosEqual(doc: Doc, other: Doc) {
+ let r = (doc[Id] === other[Id]);
+ let r2 = (doc.proto && doc.proto.Id === other[Id]);
+ let r3 = (other.proto && other.proto.Id === doc[Id]);
+ let r4 = (doc.proto && other.proto && doc.proto[Id] === other.proto[Id]);
+ return r || r2 || r3 || r4 ? true : false;
+ }
+
+ export function MakeAlias(doc: Doc) {
+ const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : undefined;
+ const alias = new Doc;
+
+ if (!proto) {
+ alias.proto = doc;
+ } else {
+ PromiseValue(Cast(doc.proto, Doc)).then(proto => {
+ if (proto) {
+ alias.proto = proto;
+ }
+ });
+ }
+
+ return alias;
+ }
+
+ export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc {
+ const copy = new Doc;
+ Object.keys(doc).forEach(key => {
+ const field = doc[key];
+ if (key === "proto" && copyProto) {
+ if (field instanceof Doc) {
+ copy[key] = Doc.MakeCopy(field);
+ }
+ } else {
+ if (field instanceof RefField) {
+ copy[key] = field;
+ } else if (field instanceof ObjectField) {
+ copy[key] = ObjectField.MakeCopy(field);
+ } else {
+ copy[key] = field;
+ }
+ }
+ });
+ return copy;
+ }
+
+ export function MakeLink(source: Doc, target: Doc) {
+ let protoSrc = source.proto ? source.proto : source;
+ let protoTarg = target.proto ? target.proto : target;
+ UndoManager.RunInBatch(() => {
+ let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 });
+ //let linkDoc = new Doc;
+ linkDoc.proto!.title = "-link name-";
+ linkDoc.proto!.linkDescription = "";
+ linkDoc.proto!.linkTags = "Default";
+
+ linkDoc.proto!.linkedTo = target;
+ linkDoc.proto!.linkedFrom = source;
+
+ let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc));
+ if (!linkedFrom) {
+ protoTarg.linkedFromDocs = linkedFrom = new List<Doc>();
+ }
+ linkedFrom.push(linkDoc);
+
+ let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc));
+ if (!linkedTo) {
+ protoSrc.linkedToDocs = linkedTo = new List<Doc>();
+ }
+ linkedTo.push(linkDoc);
+ return linkDoc;
+ }, "make link");
+ }
+
+ export function MakeDelegate(doc: Doc): Doc;
+ export function MakeDelegate(doc: Opt<Doc>): Opt<Doc>;
+ export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
+ if (!doc) {
+ return undefined;
+ }
+ const delegate = new Doc();
+ delegate.proto = doc;
+ return delegate;
+ }
+ export const Prototype = Symbol("Prototype");
+} \ No newline at end of file
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
new file mode 100644
index 000000000..d998746bb
--- /dev/null
+++ b/src/new_fields/HtmlField.ts
@@ -0,0 +1,18 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, primitive } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+
+@Deserializable("html")
+export class HtmlField extends ObjectField {
+ @serializable(primitive())
+ readonly html: string;
+
+ constructor(html: string) {
+ super();
+ this.html = html;
+ }
+
+ [Copy]() {
+ return new HtmlField(this.html);
+ }
+}
diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts
new file mode 100644
index 000000000..1a928389d
--- /dev/null
+++ b/src/new_fields/IconField.ts
@@ -0,0 +1,18 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, primitive } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+
+@Deserializable("icon")
+export class IconField extends ObjectField {
+ @serializable(primitive())
+ readonly icon: string;
+
+ constructor(icon: string) {
+ super();
+ this.icon = icon;
+ }
+
+ [Copy]() {
+ return new IconField(this.icon);
+ }
+}
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
new file mode 100644
index 000000000..2d75f8a19
--- /dev/null
+++ b/src/new_fields/InkField.ts
@@ -0,0 +1,43 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, custom, createSimpleSchema, list, object, map } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+import { deepCopy } from "../Utils";
+
+export enum InkTool {
+ None,
+ Pen,
+ Highlighter,
+ Eraser
+}
+
+export interface StrokeData {
+ pathData: Array<{ x: number, y: number }>;
+ color: string;
+ width: string;
+ tool: InkTool;
+ page: number;
+}
+
+const pointSchema = createSimpleSchema({
+ x: true, y: true
+});
+
+const strokeDataSchema = createSimpleSchema({
+ pathData: list(object(pointSchema)),
+ "*": true
+});
+
+@Deserializable("ink")
+export class InkField extends ObjectField {
+ @serializable(map(object(strokeDataSchema)))
+ readonly inkData: Map<string, StrokeData>;
+
+ constructor(data?: Map<string, StrokeData>) {
+ super();
+ this.inkData = data || new Map;
+ }
+
+ [Copy]() {
+ return new InkField(deepCopy(this.inkData));
+ }
+}
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
new file mode 100644
index 000000000..70e36f911
--- /dev/null
+++ b/src/new_fields/List.ts
@@ -0,0 +1,289 @@
+import { Deserializable, autoObject } from "../client/util/SerializationHelper";
+import { Field, Update, Self, FieldResult, SelfProxy } from "./Doc";
+import { setter, getter, deleteProperty, updateFunction } from "./util";
+import { serializable, alias, list } from "serializr";
+import { observable, action } from "mobx";
+import { ObjectField, OnUpdate, Copy, Parent } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ProxyField } from "./Proxy";
+
+const listHandlers: any = {
+ /// Mutator methods
+ copyWithin() {
+ throw new Error("copyWithin not supported yet");
+ },
+ fill(value: any, start?: number, end?: number) {
+ if (value instanceof RefField) {
+ throw new Error("fill with RefFields not supported yet");
+ }
+ const res = this[Self].__fields.fill(value, start, end);
+ this[Update]();
+ return res;
+ },
+ pop(): any {
+ const field = toRealField(this[Self].__fields.pop());
+ this[Update]();
+ return field;
+ },
+ push: action(function (this: any, ...items: any[]) {
+ items = items.map(toObjectField);
+ const list = this[Self];
+ const length = list.__fields.length;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[OnUpdate] = updateFunction(list, i + length, item, this);
+ }
+ }
+ const res = list.__fields.push(...items);
+ this[Update]();
+ return res;
+ }),
+ reverse() {
+ const res = this[Self].__fields.reverse();
+ this[Update]();
+ return res;
+ },
+ shift() {
+ const res = toRealField(this[Self].__fields.shift());
+ this[Update]();
+ return res;
+ },
+ sort(cmpFunc: any) {
+ const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+ this[Update]();
+ return res;
+ },
+ splice: action(function (this: any, start: number, deleteCount: number, ...items: any[]) {
+ items = items.map(toObjectField);
+ const list = this[Self];
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[OnUpdate] = updateFunction(list, i + start, item, this);
+ }
+ }
+ const res = list.__fields.splice(start, deleteCount, ...items);
+ this[Update]();
+ return res.map(toRealField);
+ }),
+ unshift(...items: any[]) {
+ items = items.map(toObjectField);
+ const list = this[Self];
+ const length = list.__fields.length;
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ //TODO Error checking to make sure parent doesn't already exist
+ //TODO Need to change indices of other fields in array
+ if (item instanceof ObjectField) {
+ item[Parent] = list;
+ item[OnUpdate] = updateFunction(list, i, item, this);
+ }
+ }
+ const res = this[Self].__fields.unshift(...items);
+ this[Update]();
+ return res;
+
+ },
+ /// Accessor methods
+ concat: action(function (this: any, ...items: any[]) {
+ return this[Self].__fields.map(toRealField).concat(...items);
+ }),
+ includes(valueToFind: any, fromIndex: number) {
+ const fields = this[Self].__fields;
+ if (valueToFind instanceof RefField) {
+ return fields.map(toRealField).includes(valueToFind, fromIndex);
+ } else {
+ return fields.includes(valueToFind, fromIndex);
+ }
+ },
+ indexOf(valueToFind: any, fromIndex: number) {
+ const fields = this[Self].__fields;
+ if (valueToFind instanceof RefField) {
+ return fields.map(toRealField).indexOf(valueToFind, fromIndex);
+ } else {
+ return fields.indexOf(valueToFind, fromIndex);
+ }
+ },
+ join(separator: any) {
+ return this[Self].__fields.map(toRealField).join(separator);
+ },
+ lastIndexOf(valueToFind: any, fromIndex: number) {
+ const fields = this[Self].__fields;
+ if (valueToFind instanceof RefField) {
+ return fields.map(toRealField).lastIndexOf(valueToFind, fromIndex);
+ } else {
+ return fields.lastIndexOf(valueToFind, fromIndex);
+ }
+ },
+ slice(begin: number, end: number) {
+ return this[Self].__fields.slice(begin, end).map(toRealField);
+ },
+
+ /// Iteration methods
+ entries() {
+ return this[Self].__fields.map(toRealField).entries();
+ },
+ every(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).every(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ filter(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).filter(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ find(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).find(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ findIndex(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).findIndex(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ forEach(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).forEach(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ map(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).map(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ reduce(callback: any, initialValue: any) {
+ return this[Self].__fields.map(toRealField).reduce(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ reduceRight(callback: any, initialValue: any) {
+ return this[Self].__fields.map(toRealField).reduceRight(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ some(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).some(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ values() {
+ return this[Self].__fields.map(toRealField).values();
+ },
+ [Symbol.iterator]() {
+ return this[Self].__fields.map(toRealField).values();
+ }
+};
+
+function toObjectField(field: Field) {
+ return field instanceof RefField ? new ProxyField(field) : field;
+}
+
+function toRealField(field: Field) {
+ return field instanceof ProxyField ? field.value() : field;
+}
+
+function listGetter(target: any, prop: string | number | symbol, receiver: any): any {
+ if (listHandlers.hasOwnProperty(prop)) {
+ return listHandlers[prop];
+ }
+ return getter(target, prop, receiver);
+}
+
+interface ListSpliceUpdate<T> {
+ type: "splice";
+ index: number;
+ added: T[];
+ removedCount: number;
+}
+
+interface ListIndexUpdate<T> {
+ type: "update";
+ index: number;
+ newValue: T;
+}
+
+type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
+
+type StoredType<T extends Field> = T extends RefField ? ProxyField<T> : T;
+
+@Deserializable("list")
+class ListImpl<T extends Field> extends ObjectField {
+ constructor(fields: T[] = []) {
+ super();
+ const list = new Proxy<this>(this, {
+ set: setter,
+ get: listGetter,
+ ownKeys: target => Object.keys(target.__fields),
+ getOwnPropertyDescriptor: (target, prop) => {
+ if (prop in target.__fields) {
+ return {
+ configurable: true,//TODO Should configurable be true?
+ enumerable: true,
+ };
+ }
+ return Reflect.getOwnPropertyDescriptor(target, prop);
+ },
+ deleteProperty: deleteProperty,
+ defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
+ });
+ this[SelfProxy] = list;
+ (list as any).push(...fields);
+ return list;
+ }
+
+ [key: number]: T | (T extends RefField ? Promise<T> : never);
+
+ @serializable(alias("fields", list(autoObject())))
+ private get __fields() {
+ return this.___fields;
+ }
+
+ private set __fields(value) {
+ this.___fields = value;
+ for (const key in value) {
+ const field = value[key];
+ if (!(field instanceof ObjectField)) continue;
+ (field as ObjectField)[Parent] = this[Self];
+ (field as ObjectField)[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
+ }
+ }
+
+ [Copy]() {
+ let copiedData = this[Self].__fields.map(f => f instanceof ObjectField ? f[Copy]() : f);
+ let deepCopy = new ListImpl<T>(copiedData as any);
+ return deepCopy;
+ }
+
+ // @serializable(alias("fields", list(autoObject())))
+ @observable
+ private ___fields: StoredType<T>[] = [];
+
+ private [Update] = (diff: any) => {
+ // console.log(diff);
+ const update = this[OnUpdate];
+ // update && update(diff);
+ update && update();
+ }
+
+ private [Self] = this;
+ private [SelfProxy]: any;
+}
+export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
+export const List: { new <T extends Field>(fields?: T[]): List<T> } = ListImpl as any; \ No newline at end of file
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
new file mode 100644
index 000000000..51768c6db
--- /dev/null
+++ b/src/new_fields/ObjectField.ts
@@ -0,0 +1,18 @@
+import { Doc } from "./Doc";
+import { RefField } from "./RefField";
+
+export const OnUpdate = Symbol("OnUpdate");
+export const Parent = Symbol("Parent");
+export const Copy = Symbol("Copy");
+
+export abstract class ObjectField {
+ protected [OnUpdate](diff?: any) { }
+ private [Parent]?: RefField | ObjectField;
+ abstract [Copy](): ObjectField;
+}
+
+export namespace ObjectField {
+ export function MakeCopy<T extends ObjectField>(field: T) {
+ return field[Copy]();
+ }
+}
diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts
new file mode 100644
index 000000000..fd99ae1c0
--- /dev/null
+++ b/src/new_fields/Proxy.ts
@@ -0,0 +1,65 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { FieldWaiting } from "./Doc";
+import { primitive, serializable } from "serializr";
+import { observable, action } from "mobx";
+import { DocServer } from "../client/DocServer";
+import { RefField, Id } from "./RefField";
+import { ObjectField, Copy } from "./ObjectField";
+
+@Deserializable("proxy")
+export class ProxyField<T extends RefField> extends ObjectField {
+ constructor();
+ constructor(value: T);
+ constructor(fieldId: string);
+ constructor(value?: T | string) {
+ super();
+ if (typeof value === "string") {
+ this.fieldId = value;
+ } else if (value) {
+ this.cache = value;
+ this.fieldId = value[Id];
+ }
+ }
+
+ [Copy]() {
+ if (this.cache) return new ProxyField<T>(this.cache);
+ return new ProxyField<T>(this.fieldId);
+ }
+
+ @serializable(primitive())
+ readonly fieldId: string = "";
+
+ // This getter/setter and nested object thing is
+ // because mobx doesn't play well with observable proxies
+ @observable.ref
+ private _cache: { readonly field: T | undefined } = { field: undefined };
+ private get cache(): T | undefined {
+ return this._cache.field;
+ }
+ private set cache(field: T | undefined) {
+ this._cache = { field };
+ }
+
+ private failed = false;
+ private promise?: Promise<any>;
+
+ value(callback?: ((field: T | undefined) => void)): T | undefined | FieldWaiting {
+ if (this.cache) {
+ callback && callback(this.cache);
+ return this.cache;
+ }
+ if (this.failed) {
+ return undefined;
+ }
+ if (!this.promise) {
+ this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => {
+ this.promise = undefined;
+ this.cache = field;
+ if (field === undefined) this.failed = true;
+ return field;
+ }));
+ }
+ callback && this.promise.then(callback);
+ return this.promise;
+ }
+}
diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts
new file mode 100644
index 000000000..202c65f21
--- /dev/null
+++ b/src/new_fields/RefField.ts
@@ -0,0 +1,18 @@
+import { serializable, primitive, alias } from "serializr";
+import { Utils } from "../Utils";
+
+export type FieldId = string;
+export const HandleUpdate = Symbol("HandleUpdate");
+export const Id = Symbol("Id");
+export abstract class RefField {
+ @serializable(alias("id", primitive()))
+ private __id: FieldId;
+ readonly [Id]: FieldId;
+
+ constructor(id?: FieldId) {
+ this.__id = id || Utils.GenerateGuid();
+ this[Id] = this.__id;
+ }
+
+ protected [HandleUpdate]?(diff: any): void;
+}
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
new file mode 100644
index 000000000..eb30e76de
--- /dev/null
+++ b/src/new_fields/RichTextField.ts
@@ -0,0 +1,18 @@
+import { ObjectField, Copy } from "./ObjectField";
+import { serializable } from "serializr";
+import { Deserializable } from "../client/util/SerializationHelper";
+
+@Deserializable("RichTextField")
+export class RichTextField extends ObjectField {
+ @serializable(true)
+ readonly Data: string;
+
+ constructor(data: string) {
+ super();
+ this.Data = data;
+ }
+
+ [Copy]() {
+ return new RichTextField(this.Data);
+ }
+} \ No newline at end of file
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
new file mode 100644
index 000000000..b821baec9
--- /dev/null
+++ b/src/new_fields/Schema.ts
@@ -0,0 +1,82 @@
+import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType } from "./Types";
+import { Doc, Field } from "./Doc";
+
+type AllToInterface<T extends Interface[]> = {
+ 1: ToInterface<Head<T>> & AllToInterface<Tail<T>>,
+ 0: ToInterface<Head<T>>
+}[HasTail<T> extends true ? 1 : 0];
+
+export const emptySchema = createSchema({});
+export const Document = makeInterface(emptySchema);
+export type Document = makeInterface<[typeof emptySchema]>;
+
+export type makeInterface<T extends Interface[]> = Partial<AllToInterface<T>> & Doc & { proto: Doc | undefined };
+// export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
+// export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>;
+export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc) => makeInterface<T> {
+ let schema: Interface = {};
+ for (const s of schemas) {
+ for (const key in s) {
+ schema[key] = s[key];
+ }
+ }
+ const proto = new Proxy({}, {
+ get(target: any, prop, receiver) {
+ const field = receiver.doc[prop];
+ if (prop in schema) {
+ return Cast(field, (schema as any)[prop]);
+ }
+ return field;
+ },
+ set(target: any, prop, value, receiver) {
+ receiver.doc[prop] = value;
+ return true;
+ }
+ });
+ return function (doc?: Doc) {
+ doc = doc || new Doc;
+ if (!(doc instanceof Doc)) {
+ throw new Error("Currently wrapping a schema in another schema isn't supported");
+ }
+ const obj = Object.create(proto, { doc: { value: doc, writable: false } });
+ return obj;
+ };
+}
+
+export type makeStrictInterface<T extends Interface> = Partial<ToInterface<T>>;
+export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc) => makeStrictInterface<T> {
+ const proto = {};
+ for (const key in schema) {
+ const type = schema[key];
+ Object.defineProperty(proto, key, {
+ get() {
+ return Cast(this.__doc[key], type as any);
+ },
+ set(value) {
+ value = Cast(value, type as any);
+ if (value !== undefined) {
+ this.__doc[key] = value;
+ return;
+ }
+ throw new TypeError("Expected type " + type);
+ }
+ });
+ }
+ return function (doc: any) {
+ if (!(doc instanceof Doc)) {
+ throw new Error("Currently wrapping a schema in another schema isn't supported");
+ }
+ const obj = Object.create(proto);
+ obj.__doc = doc;
+ return obj;
+ };
+}
+
+export function createSchema<T extends Interface>(schema: T): T & { proto: ToConstructor<Doc> } {
+ schema.proto = Doc;
+ return schema as any;
+}
+
+export function listSpec<U extends ToConstructor<Field>>(type: U): ListSpec<ToType<U>> {
+ return { List: type as any };//TODO Types
+} \ No newline at end of file
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
new file mode 100644
index 000000000..4b4c58eb8
--- /dev/null
+++ b/src/new_fields/Types.ts
@@ -0,0 +1,88 @@
+import { Field, Opt, FieldResult, Doc } from "./Doc";
+import { List } from "./List";
+import { RefField } from "./RefField";
+
+export type ToType<T extends ToConstructor<Field> | ListSpec<Field>> =
+ T extends "string" ? string :
+ T extends "number" ? number :
+ T extends "boolean" ? boolean :
+ T extends ListSpec<infer U> ? List<U> :
+ // T extends { new(...args: any[]): infer R } ? (R | Promise<R>) : never;
+ T extends { new(...args: any[]): List<Field> } ? never :
+ T extends { new(...args: any[]): infer R } ? R : never;
+
+export type ToConstructor<T extends Field> =
+ T extends string ? "string" :
+ T extends number ? "number" :
+ T extends boolean ? "boolean" :
+ T extends List<infer U> ? ListSpec<U> :
+ new (...args: any[]) => T;
+
+export type ToInterface<T extends Interface> = {
+ [P in Exclude<keyof T, "proto">]: FieldResult<ToType<T[P]>>;
+};
+
+// type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> };
+export type ListSpec<T extends Field> = { List: ToConstructor<T> };
+
+// type ListType<U extends Field[]> = { 0: List<ListType<Tail<U>>>, 1: ToType<Head<U>> }[HasTail<U> extends true ? 0 : 1];
+
+export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
+export type Tail<T extends any[]> =
+ ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
+export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
+
+//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
+export interface Interface {
+ [key: string]: ToConstructor<Field> | ListSpec<Field>;
+ // [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
+}
+
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T): FieldResult<ToType<T>>;
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal: WithoutList<ToType<T>> | null): WithoutList<ToType<T>>;
+export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal?: ToType<T> | null): FieldResult<ToType<T>> | undefined {
+ if (field instanceof Promise) {
+ return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal === null ? undefined : defaultVal;
+ }
+ if (field !== undefined && !(field instanceof Promise)) {
+ if (typeof ctor === "string") {
+ if (typeof field === ctor) {
+ return field as ToType<T>;
+ }
+ } else if (typeof ctor === "object") {
+ if (field instanceof List) {
+ return field as any;
+ }
+ } else if (field instanceof (ctor as any)) {
+ return field as ToType<T>;
+ }
+ }
+ return defaultVal === null ? undefined : defaultVal;
+}
+
+export function NumCast(field: FieldResult, defaultVal: number | null = 0) {
+ return Cast(field, "number", defaultVal);
+}
+
+export function StrCast(field: FieldResult, defaultVal: string | null = "") {
+ return Cast(field, "string", defaultVal);
+}
+
+export function BoolCast(field: FieldResult, defaultVal: boolean | null = null) {
+ return Cast(field, "boolean", defaultVal);
+}
+
+type WithoutList<T extends Field> = T extends List<infer R> ? (R extends RefField ? (R | Promise<R>)[] : R[]) : T;
+
+export function FieldValue<T extends Field, U extends WithoutList<T>>(field: FieldResult<T>, defaultValue: U): WithoutList<T>;
+export function FieldValue<T extends Field>(field: FieldResult<T>): Opt<T>;
+export function FieldValue<T extends Field>(field: FieldResult<T>, defaultValue?: T): Opt<T> {
+ return (field instanceof Promise || field === undefined) ? defaultValue : field;
+}
+
+export interface PromiseLike<T> {
+ then(callback: (field: Opt<T>) => void): void;
+}
+export function PromiseValue<T extends Field>(field: FieldResult<T>): PromiseLike<Opt<T>> {
+ return field instanceof Promise ? field : { then(cb: ((field: Opt<T>) => void)) { return cb(field); } };
+} \ No newline at end of file
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
new file mode 100644
index 000000000..d00a95a16
--- /dev/null
+++ b/src/new_fields/URLField.ts
@@ -0,0 +1,34 @@
+import { Deserializable } from "../client/util/SerializationHelper";
+import { serializable, custom } from "serializr";
+import { ObjectField, Copy } from "./ObjectField";
+
+function url() {
+ return custom(
+ function (value: URL) {
+ return value.href;
+ },
+ function (jsonValue: string) {
+ return new URL(jsonValue);
+ }
+ );
+}
+
+export class URLField extends ObjectField {
+ @serializable(url())
+ readonly url: URL;
+
+ constructor(url: URL) {
+ super();
+ this.url = url;
+ }
+
+ [Copy](): this {
+ return new (this.constructor as any)(this.url);
+ }
+}
+
+@Deserializable("audio") export class AudioField extends URLField { }
+@Deserializable("image") export class ImageField extends URLField { }
+@Deserializable("video") export class VideoField extends URLField { }
+@Deserializable("pdf") export class PdfField extends URLField { }
+@Deserializable("web") export class WebField extends URLField { } \ No newline at end of file
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
new file mode 100644
index 000000000..3a16a6b42
--- /dev/null
+++ b/src/new_fields/util.ts
@@ -0,0 +1,111 @@
+import { UndoManager } from "../client/util/UndoManager";
+import { Update, Doc, Field } from "./Doc";
+import { SerializationHelper } from "../client/util/SerializationHelper";
+import { ProxyField } from "./Proxy";
+import { FieldValue } from "./Types";
+import { RefField, Id } from "./RefField";
+import { ObjectField, Parent, OnUpdate } from "./ObjectField";
+import { action } from "mobx";
+
+export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
+ if (SerializationHelper.IsSerializing()) {
+ target[prop] = value;
+ return true;
+ }
+ if (typeof prop === "symbol") {
+ target[prop] = value;
+ return true;
+ }
+ const curValue = target.__fields[prop];
+ if (curValue === value || (curValue instanceof ProxyField && value instanceof RefField && curValue.fieldId === value[Id])) {
+ // TODO This kind of checks correctly in the case that curValue is a ProxyField and value is a RefField, but technically
+ // curValue should get filled in with value if it isn't already filled in, in case we fetched the referenced field some other way
+ return true;
+ }
+ if (value instanceof RefField) {
+ value = new ProxyField(value);
+ }
+ if (value instanceof ObjectField) {
+ //TODO Instead of target, maybe use target[Self]
+ if (value[Parent] && value[Parent] !== target) {
+ throw new Error("Can't put the same object in multiple documents at the same time");
+ }
+ value[Parent] = target;
+ value[OnUpdate] = updateFunction(target, prop, value, receiver);
+ }
+ if (curValue instanceof ObjectField) {
+ delete curValue[Parent];
+ delete curValue[OnUpdate];
+ }
+ target.__fields[prop] = value;
+ target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
+ UndoManager.AddEvent({
+ redo: () => receiver[prop] = value,
+ undo: () => receiver[prop] = curValue
+ });
+ return true;
+});
+
+export function getter(target: any, prop: string | symbol | number, receiver: any): any {
+ if (typeof prop === "symbol") {
+ return target.__fields[prop] || target[prop];
+ }
+ if (SerializationHelper.IsSerializing()) {
+ return target[prop];
+ }
+ return getField(target, prop);
+}
+function getProtoField(protoField: Doc | undefined, prop: string | number, cb?: (field: Field | undefined) => void) {
+ if (!protoField) return undefined;
+ let field = protoField[prop];
+ if (field instanceof Promise) {
+ cb && field.then(cb);
+ return field;
+ } else {
+ cb && cb(field);
+ return field;
+ }
+}
+
+//TODO The callback parameter is never being passed in currently, so we should be able to get rid of it.
+export function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any {
+ const field = target.__fields[prop];
+ if (field instanceof ProxyField) {
+ return field.value(callback);
+ }
+ if (field === undefined && !ignoreProto) {
+ const proto = getField(target, "proto", true);
+ if (proto instanceof Doc) {
+ return getProtoField(proto, prop, callback);
+ } else if (proto instanceof Promise) {
+ return proto.then(async proto => getProtoField(proto, prop, callback));
+ }
+ }
+ callback && callback(field);
+ return field;
+}
+
+export function deleteProperty(target: any, prop: string | number | symbol) {
+ if (typeof prop === "symbol") {
+ delete target[prop];
+ return true;
+ }
+ throw new Error("Currently properties can't be deleted from documents, assign to undefined instead");
+}
+
+export function updateFunction(target: any, prop: any, value: any, receiver: any) {
+ let current = ObjectField.MakeCopy(value);
+ return (diff?: any) => {
+ if (true || !diff) {
+ diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
+ const oldValue = current;
+ const newValue = ObjectField.MakeCopy(value);
+ current = newValue;
+ UndoManager.AddEvent({
+ redo() { receiver[prop] = newValue; },
+ undo() { receiver[prop] = oldValue; }
+ });
+ }
+ target[Update](diff);
+ };
+} \ No newline at end of file
diff --git a/src/server/Client.ts b/src/server/Client.ts
index 6b8841658..e6f953712 100644
--- a/src/server/Client.ts
+++ b/src/server/Client.ts
@@ -1,15 +1,11 @@
import { computed } from "mobx";
export class Client {
- constructor(guid: string) {
- this.guid = guid
- }
+ private _guid: string;
- private guid: string;
-
- @computed
- public get GUID(): string {
- return this.guid
+ constructor(guid: string) {
+ this._guid = guid;
}
+ @computed public get GUID(): string { return this._guid; }
} \ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 8a00f6b59..e9a8b0f0c 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,125 +1,48 @@
import { Utils } from "../Utils";
export class Message<T> {
- private name: string;
- private guid: string;
-
- get Name(): string {
- return this.name;
- }
-
- get Message(): string {
- return this.guid
- }
+ private _name: string;
+ private _guid: string;
constructor(name: string) {
- this.name = name;
- this.guid = Utils.GenerateDeterministicGuid(name)
- }
-
- GetValue() {
- return this.Name;
- }
-}
-
-class TestMessageArgs {
- hello: string = "";
-}
-
-export class SetFieldArgs {
- field: string;
- value: any;
-
- constructor(f: string, v: any) {
- this.field = f
- this.value = v
+ this._name = name;
+ this._guid = Utils.GenerateDeterministicGuid(name);
}
-}
-export class GetFieldArgs {
- field: string;
-
- constructor(f: string) {
- this.field = f
- }
+ get Name(): string { return this._name; }
+ get Message(): string { return this._guid; }
}
export enum Types {
- Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html, Video, Audio, Ink, PDF
-}
-
-export class DocumentTransfer implements Transferable {
- readonly type = Types.Document
- _id: string
-
- constructor(readonly obj: { type: Types, data: [string, string][], _id: string }) {
- this._id = obj._id
- }
-}
-
-export class ImageTransfer implements Transferable {
- readonly type = Types.Image
-
- constructor(readonly _id: string) { }
-}
-
-export class KeyTransfer implements Transferable {
- name: string
- readonly _id: string
- readonly type = Types.Key
-
- constructor(i: string, n: string) {
- this.name = n
- this._id = i
- }
-}
-
-export class ListTransfer implements Transferable {
- type = Types.List;
-
- constructor(readonly _id: string) { }
-}
-
-export class NumberTransfer implements Transferable {
- readonly type = Types.Number
-
- constructor(readonly value: number, readonly _id: string) { }
+ Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
+ Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Templates
}
-export class TextTransfer implements Transferable {
- value: string
- readonly _id: string
- readonly type = Types.Text
-
- constructor(t: string, i: string) {
- this.value = t
- this._id = i
- }
+export interface Transferable {
+ readonly id: string;
+ readonly type: Types;
+ readonly data?: any;
}
-export class RichTextTransfer implements Transferable {
- value: string
- readonly _id: string
- readonly type = Types.Text
-
- constructor(t: string, i: string) {
- this.value = t
- this._id = i
- }
+export interface Reference {
+ readonly id: string;
}
-export interface Transferable {
- readonly _id: string
- readonly type: Types
+export interface Diff extends Reference {
+ readonly diff: any;
}
export namespace MessageStore {
export const Foo = new Message<string>("Foo");
export const Bar = new Message<string>("Bar");
- export const AddDocument = new Message<DocumentTransfer>("Add Document");
- export const SetField = new Message<{ _id: string, data: any, type: Types }>("Set Field")
- export const GetField = new Message<string>("Get Field")
- export const GetFields = new Message<string[]>("Get Fields")
+ export const SetField = new Message<Transferable>("Set Field"); // send Transferable (no reply)
+ export const GetField = new Message<string>("Get Field"); // send string 'id' get Transferable back
+ export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
export const GetDocument = new Message<string>("Get Document");
export const DeleteAll = new Message<any>("Delete All");
-} \ No newline at end of file
+
+ export const GetRefField = new Message<string>("Get Ref Field");
+ export const GetRefFields = new Message<string[]>("Get Ref Fields");
+ export const UpdateField = new Message<Diff>("Update Ref Field");
+ export const CreateField = new Message<Reference>("Create Ref Field");
+}
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
new file mode 100644
index 000000000..fdf5b6a5c
--- /dev/null
+++ b/src/server/RouteStore.ts
@@ -0,0 +1,30 @@
+// PREPEND ALL ROUTES WITH FORWARD SLASHES!
+
+export enum RouteStore {
+ // GENERAL
+ root = "/",
+ home = "/home",
+ corsProxy = "/corsProxy",
+ delete = "/delete",
+ deleteAll = "/deleteAll",
+
+ // UPLOAD AND STATIC FILE SERVING
+ public = "/public",
+ upload = "/upload",
+ images = "/images",
+
+ // USER AND WORKSPACES
+ getCurrUser = "/getCurrentUser",
+ getUserDocumentId = "/getUserDocumentId",
+ updateCursor = "/updateCursor",
+
+ openDocumentWithId = "/doc/:docId",
+
+ // AUTHENTICATION
+ signup = "/signup",
+ login = "/login",
+ logout = "/logout",
+ forgot = "/forgotpassword",
+ reset = "/reset/:token",
+
+} \ No newline at end of file
diff --git a/src/server/Search.ts b/src/server/Search.ts
new file mode 100644
index 000000000..5ca5578a7
--- /dev/null
+++ b/src/server/Search.ts
@@ -0,0 +1,49 @@
+import * as rp from 'request-promise';
+import { Database } from './database';
+import { thisExpression } from 'babel-types';
+
+export class Search {
+ public static Instance = new Search();
+ private url = 'http://localhost:8983/solr/';
+
+ public async updateDocument(document: any) {
+ try {
+ const res = await rp.post(this.url + "dash/update", {
+ headers: { 'content-type': 'application/json' },
+ body: JSON.stringify([document])
+ });
+ return res;
+ } catch (e) {
+ console.warn("Search error: " + e + document);
+ }
+ }
+
+ public async search(query: string) {
+ try {
+ const searchResults = JSON.parse(await rp.get(this.url + "dash/select", {
+ qs: {
+ q: query,
+ fl: "id"
+ }
+ }));
+ const fields = searchResults.response.docs;
+ const ids = fields.map((field: any) => field.id);
+ return ids;
+ } catch {
+ return [];
+ }
+ }
+
+ public async clear() {
+ try {
+ return await rp.post(this.url + "dash/update", {
+ body: {
+ delete: {
+ query: "*:*"
+ }
+ },
+ json: true
+ });
+ } catch { }
+ }
+} \ No newline at end of file
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
deleted file mode 100644
index 5331c9e30..000000000
--- a/src/server/ServerUtil.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-
-import { Field } from './../fields/Field';
-import { TextField } from './../fields/TextField';
-import { NumberField } from './../fields/NumberField';
-import { RichTextField } from './../fields/RichTextField';
-import { Key } from './../fields/Key';
-import { ImageField } from './../fields/ImageField';
-import { ListField } from './../fields/ListField';
-import { Document } from './../fields/Document';
-import { Server } from './../client/Server';
-import { Types } from './Message';
-import { Utils } from '../Utils';
-import { HtmlField } from '../fields/HtmlField';
-import { WebField } from '../fields/WebField';
-import { AudioField } from '../fields/AudioField';
-import { VideoField } from '../fields/VideoField';
-import {InkField} from '../fields/InkField';
-import {PDFField} from '../fields/PDFField';
-
-
-
-
-export class ServerUtils {
- public static FromJson(json: any): Field {
- let obj = json
- let data: any = obj.data
- let id: string = obj._id
- let type: Types = obj.type
-
- if (!(data !== undefined && id && type !== undefined)) {
- console.log("how did you manage to get an object that doesn't have a data or an id?")
- return new TextField("Something to fill the space", Utils.GenerateGuid());
- }
-
- switch (type) {
- case Types.Number:
- return new NumberField(data, id, false)
- case Types.Text:
- return new TextField(data, id, false)
- case Types.Html:
- return new HtmlField(data, id, false)
- case Types.Web:
- return new WebField(new URL(data), id, false)
- case Types.RichText:
- return new RichTextField(data, id, false)
- case Types.Key:
- return new Key(data, id, false)
- case Types.Image:
- return new ImageField(new URL(data), id, false)
- case Types.PDF:
- return new PDFField(new URL(data), id, false)
- case Types.List:
- return ListField.FromJson(id, data)
- case Types.Audio:
- return new AudioField(new URL(data), id, false)
- case Types.Video:
- return new VideoField(new URL(data), id, false)
- case Types.Ink:
- return InkField.FromJson(id, data);
- case Types.Document:
- let doc: Document = new Document(id, false)
- let fields: [string, string][] = data as [string, string][]
- fields.forEach(element => {
- doc._proxies.set(element[0], element[1]);
- });
- return doc
- default:
- throw Error("Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here");
- }
- }
-} \ No newline at end of file
diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts
index 05f6c3133..d42741410 100644
--- a/src/server/authentication/config/passport.ts
+++ b/src/server/authentication/config/passport.ts
@@ -1,9 +1,10 @@
-import * as passport from 'passport'
+import * as passport from 'passport';
import * as passportLocal from 'passport-local';
import * as mongodb from 'mongodb';
import * as _ from "lodash";
-import { default as User } from '../models/User';
+import { default as User } from '../models/user_model';
import { Request, Response, NextFunction } from "express";
+import { RouteStore } from '../../RouteStore';
const LocalStrategy = passportLocal.Strategy;
@@ -18,10 +19,10 @@ passport.deserializeUser<any, any>((id, done) => {
});
// AUTHENTICATE JUST WITH EMAIL AND PASSWORD
-passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
+passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => {
if (error) return done(error);
- if (!user) return done(undefined, false, { message: "Invalid email or password" }) // invalid email
+ if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email
user.comparePassword(password, (error: Error, isMatch: boolean) => {
if (error) return done(error);
if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password
@@ -35,8 +36,8 @@ export let isAuthenticated = (req: Request, res: Response, next: NextFunction) =
if (req.isAuthenticated()) {
return next();
}
- return res.redirect("/login");
-}
+ return res.redirect(RouteStore.login);
+};
export let isAuthorized = (req: Request, res: Response, next: NextFunction) => {
const provider = req.path.split("/").slice(-1)[0];
diff --git a/src/server/authentication/controllers/user.ts b/src/server/authentication/controllers/user.ts
deleted file mode 100644
index f74ff9039..000000000
--- a/src/server/authentication/controllers/user.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { default as User, UserModel, AuthToken } from "../models/User";
-import { Request, Response, NextFunction } from "express";
-import * as passport from "passport";
-import { IVerifyOptions } from "passport-local";
-import "../config/passport";
-import * as request from "express-validator";
-const flash = require("express-flash");
-import * as session from "express-session";
-import * as pug from 'pug';
-
-/**
- * GET /signup
- * Signup page.
- */
-export let getSignup = (req: Request, res: Response) => {
- if (req.user) {
- return res.redirect("/");
- }
- res.render("signup.pug", {
- title: "Sign Up"
- });
-};
-
-/**
- * POST /signup
- * Create a new local account.
- */
-export let postSignup = (req: Request, res: Response, next: NextFunction) => {
- req.assert("email", "Email is not valid").isEmail();
- req.assert("password", "Password must be at least 4 characters long").len({ min: 4 });
- req.assert("confirmPassword", "Passwords do not match").equals(req.body.password);
- req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
-
- const errors = req.validationErrors();
-
- if (errors) {
- req.flash("errors", "Unable to facilitate sign up. Please try again.");
- return res.redirect("/signup");
- }
-
- const user = new User({
- email: req.body.email,
- password: req.body.password
- });
-
- User.findOne({ email: req.body.email }, (err, existingUser) => {
- if (err) { return next(err); }
- if (existingUser) {
- req.flash("errors", "Account with that email address already exists.");
- return res.redirect("/signup");
- }
- user.save((err) => {
- if (err) { return next(err); }
- req.logIn(user, (err) => {
- if (err) {
- return next(err);
- }
- res.redirect("/");
- });
- });
- });
-};
-
-
-/**
- * GET /login
- * Login page.
- */
-export let getLogin = (req: Request, res: Response) => {
- if (req.user) {
- return res.redirect("/");
- }
- res.send("<p>dear lord please render</p>");
- // res.render("account/login", {
- // title: "Login"
- // });
-};
-
-/**
- * POST /login
- * Sign in using email and password.
- */
-export let postLogin = (req: Request, res: Response, next: NextFunction) => {
- req.assert("email", "Email is not valid").isEmail();
- req.assert("password", "Password cannot be blank").notEmpty();
- req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
-
- const errors = req.validationErrors();
-
- if (errors) {
- req.flash("errors", "Unable to login at this time. Please try again.");
- return res.redirect("/login");
- }
-
- passport.authenticate("local", (err: Error, user: UserModel, info: IVerifyOptions) => {
- if (err) { return next(err); }
- if (!user) {
- req.flash("errors", info.message);
- return res.redirect("/login");
- }
- req.logIn(user, (err) => {
- if (err) { return next(err); }
- req.flash("success", "Success! You are logged in.");
- res.redirect("/");
- });
- })(req, res, next);
-}; \ No newline at end of file
diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts
new file mode 100644
index 000000000..1dacdf3fa
--- /dev/null
+++ b/src/server/authentication/controllers/user_controller.ts
@@ -0,0 +1,266 @@
+import { default as User, DashUserModel, AuthToken } from "../models/user_model";
+import { Request, Response, NextFunction } from "express";
+import * as passport from "passport";
+import { IVerifyOptions } from "passport-local";
+import "../config/passport";
+import * as request from "express-validator";
+import flash = require("express-flash");
+import * as session from "express-session";
+import * as pug from 'pug';
+import * as async from 'async';
+import * as nodemailer from 'nodemailer';
+import c = require("crypto");
+import { RouteStore } from "../../RouteStore";
+import { Utils } from "../../../Utils";
+
+/**
+ * GET /signup
+ * Directs user to the signup page
+ * modeled by signup.pug in views
+ */
+export let getSignup = (req: Request, res: Response) => {
+ if (req.user) {
+ let user = req.user;
+ return res.redirect(RouteStore.home);
+ }
+ res.render("signup.pug", {
+ title: "Sign Up",
+ user: req.user,
+ });
+};
+
+/**
+ * POST /signup
+ * Create a new local account.
+ */
+export let postSignup = (req: Request, res: Response, next: NextFunction) => {
+ req.assert("email", "Email is not valid").isEmail();
+ req.assert("password", "Password must be at least 4 characters long").len({ min: 4 });
+ req.assert("confirmPassword", "Passwords do not match").equals(req.body.password);
+ req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
+
+ const errors = req.validationErrors();
+
+ if (errors) {
+ res.render("signup.pug", {
+ title: "Sign Up",
+ user: req.user,
+ });
+ return res.redirect(RouteStore.signup);
+ }
+
+ const email = req.body.email;
+ const password = req.body.password;
+
+ const user = new User({
+ email,
+ password,
+ userDocumentId: Utils.GenerateGuid()
+ });
+
+ User.findOne({ email }, (err, existingUser) => {
+ if (err) { return next(err); }
+ if (existingUser) {
+ return res.redirect(RouteStore.login);
+ }
+ user.save((err) => {
+ if (err) { return next(err); }
+ req.logIn(user, (err) => {
+ if (err) {
+ return next(err);
+ }
+ res.redirect(RouteStore.home);
+ });
+ });
+ });
+
+};
+
+
+/**
+ * GET /login
+ * Login page.
+ */
+export let getLogin = (req: Request, res: Response) => {
+ if (req.user) {
+ return res.redirect(RouteStore.home);
+ }
+ res.render("login.pug", {
+ title: "Log In",
+ user: req.user
+ });
+};
+
+/**
+ * POST /login
+ * Sign in using email and password.
+ * On failure, redirect to signup page
+ */
+export let postLogin = (req: Request, res: Response, next: NextFunction) => {
+ req.assert("email", "Email is not valid").isEmail();
+ req.assert("password", "Password cannot be blank").notEmpty();
+ req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
+
+ const errors = req.validationErrors();
+
+ if (errors) {
+ req.flash("errors", "Unable to login at this time. Please try again.");
+ return res.redirect(RouteStore.signup);
+ }
+
+ passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => {
+ if (err) { next(err); return; }
+ if (!user) {
+ return res.redirect(RouteStore.signup);
+ }
+ req.logIn(user, (err) => {
+ if (err) { next(err); return; }
+ res.redirect(RouteStore.home);
+ });
+ })(req, res, next);
+};
+
+/**
+ * GET /logout
+ * Invokes the logout function on the request
+ * and destroys the user's current session.
+ */
+export let getLogout = (req: Request, res: Response) => {
+ req.logout();
+ const sess = req.session;
+ if (sess) {
+ sess.destroy((err) => { if (err) { console.log(err); } });
+ }
+ res.redirect(RouteStore.login);
+};
+
+export let getForgot = function (req: Request, res: Response) {
+ res.render("forgot.pug", {
+ title: "Recover Password",
+ user: req.user,
+ });
+};
+
+export let postForgot = function (req: Request, res: Response, next: NextFunction) {
+ const email = req.body.email;
+ async.waterfall([
+ function (done: any) {
+ let token: string;
+ c.randomBytes(20, function (err: any, buffer: Buffer) {
+ if (err) {
+ done(null);
+ return;
+ }
+ done(null, buffer.toString('hex'));
+ });
+ },
+ function (token: string, done: any) {
+ User.findOne({ email }, function (err, user: DashUserModel) {
+ if (!user) {
+ // NO ACCOUNT WITH SUBMITTED EMAIL
+ res.redirect(RouteStore.forgot);
+ return;
+ }
+ user.passwordResetToken = token;
+ user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
+ user.save(function (err: any) {
+ done(null, token, user);
+ });
+ });
+ },
+ function (token: Uint16Array, user: DashUserModel, done: any) {
+ const smtpTransport = nodemailer.createTransport({
+ service: 'Gmail',
+ auth: {
+ user: 'brownptcdash@gmail.com',
+ pass: 'browngfx1'
+ }
+ });
+ const mailOptions = {
+ to: user.email,
+ from: 'brownptcdash@gmail.com',
+ subject: 'Dash Password Reset',
+ text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
+ 'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
+ 'http://' + req.headers.host + '/reset/' + token + '\n\n' +
+ 'If you did not request this, please ignore this email and your password will remain unchanged.\n'
+ };
+ smtpTransport.sendMail(mailOptions, function (err) {
+ // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.');
+ done(null, err, 'done');
+ });
+ }
+ ], function (err) {
+ if (err) return next(err);
+ res.redirect(RouteStore.forgot);
+ });
+};
+
+export let getReset = function (req: Request, res: Response) {
+ User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) {
+ if (!user || err) {
+ return res.redirect(RouteStore.forgot);
+ }
+ res.render("reset.pug", {
+ title: "Reset Password",
+ user: req.user,
+ });
+ });
+};
+
+export let postReset = function (req: Request, res: Response) {
+ async.waterfall([
+ function (done: any) {
+ User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) {
+ if (!user || err) {
+ return res.redirect('back');
+ }
+
+ req.assert("password", "Password must be at least 4 characters long").len({ min: 4 });
+ req.assert("confirmPassword", "Passwords do not match").equals(req.body.password);
+
+ if (req.validationErrors()) {
+ return res.redirect('back');
+ }
+
+ user.password = req.body.password;
+ user.passwordResetToken = undefined;
+ user.passwordResetExpires = undefined;
+
+ user.save(function (err) {
+ if (err) {
+ res.redirect(RouteStore.login);
+ return;
+ }
+ req.logIn(user, function (err) {
+ if (err) {
+ return;
+ }
+ });
+ done(null, user);
+ });
+ });
+ },
+ function (user: DashUserModel, done: any) {
+ const smtpTransport = nodemailer.createTransport({
+ service: 'Gmail',
+ auth: {
+ user: 'brownptcdash@gmail.com',
+ pass: 'browngfx1'
+ }
+ });
+ const mailOptions = {
+ to: user.email,
+ from: 'brownptcdash@gmail.com',
+ subject: 'Your password has been changed',
+ text: 'Hello,\n\n' +
+ 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
+ };
+ smtpTransport.sendMail(mailOptions, function (err) {
+ done(null, err);
+ });
+ }
+ ], function (err) {
+ res.redirect(RouteStore.login);
+ });
+}; \ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
new file mode 100644
index 000000000..aef2d3f4a
--- /dev/null
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -0,0 +1,137 @@
+import { computed, observable, action, runInAction } from "mobx";
+import * as rp from 'request-promise';
+import { Docs } from "../../../client/documents/Documents";
+import { Attribute, AttributeGroup, Catalog, Schema, AggregateFunction } from "../../../client/northstar/model/idea/idea";
+import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
+import { RouteStore } from "../../RouteStore";
+import { DocServer } from "../../../client/DocServer";
+import { Doc, Opt, Field } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
+import { CollectionTreeView } from "../../../client/views/collections/CollectionTreeView";
+import { CollectionView } from "../../../client/views/collections/CollectionView";
+import { NorthstarSettings, Gateway } from "../../../client/northstar/manager/Gateway";
+import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
+import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
+import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
+import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
+
+export class CurrentUserUtils {
+ private static curr_email: string;
+ private static curr_id: string;
+ @observable private static user_document: Doc;
+ //TODO tfs: these should be temporary...
+ private static mainDocId: string | undefined;
+
+ public static get email() { return this.curr_email; }
+ public static get id() { return this.curr_id; }
+ @computed public static get UserDocument() { return this.user_document; }
+ public static get MainDocId() { return this.mainDocId; }
+ public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
+
+ private static createUserDocument(id: string): Doc {
+ let doc = new Doc(id, true);
+ doc.viewType = CollectionViewType.Tree;
+ doc.layout = CollectionView.LayoutString();
+ doc.title = this.email;
+ doc.data = new List<Doc>();
+ doc.excludeFromLibrary = true;
+ doc.optionalRightCollection = Docs.SchemaDocument(["title"], [], { title: "Pending documents" });
+ // doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
+ // (doc.library as Doc).excludeFromLibrary = true;
+ return doc;
+ }
+
+ public static async loadCurrentUser(): Promise<any> {
+ let userPromise = rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => {
+ if (response) {
+ let obj = JSON.parse(response);
+ CurrentUserUtils.curr_id = obj.id as string;
+ CurrentUserUtils.curr_email = obj.email as string;
+ } else {
+ throw new Error("There should be a user! Why does Dash think there isn't one?");
+ }
+ });
+ let userDocPromise = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
+ if (id) {
+ return DocServer.GetRefField(id).then(field =>
+ runInAction(() => this.user_document = field instanceof Doc ? field : this.createUserDocument(id)));
+ } else {
+ throw new Error("There should be a user id! Why does Dash think there isn't one?");
+ }
+ });
+ try {
+ const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
+ NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
+ await Gateway.Instance.ClearCatalog();
+ const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
+ let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
+ let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
+ if (catprom) await Promise.all(catprom);
+ } catch (e) {
+
+ }
+ return Promise.all([userPromise, userDocPromise]);
+ }
+
+ /* Northstar catalog ... really just for testing so this should eventually go away */
+ // --------------- Northstar hooks ------------- /
+ static _northstarSchemas: Doc[] = [];
+ @observable private static _northstarCatalog?: Catalog;
+ @computed public static get NorthstarDBCatalog() { return this._northstarCatalog; }
+
+ @action static SetNorthstarCatalog(ctlog: Catalog, extras: Catalog[]) {
+ CurrentUserUtils.NorthstarDBCatalog = ctlog;
+ if (ctlog && ctlog.schemas) {
+ extras.map(ex => ctlog.schemas!.push(ex));
+ return ctlog.schemas.map(async schema => {
+ let schemaDocuments: Doc[] = [];
+ let attributesToBecomeDocs = CurrentUserUtils.GetAllNorthstarColumnAttributes(schema);
+ await Promise.all(attributesToBecomeDocs.reduce((promises, attr) => {
+ promises.push(DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
+ if (field instanceof Doc) {
+ schemaDocuments.push(field);
+ } else {
+ var atmod = new ColumnAttributeModel(attr);
+ let histoOp = new HistogramOperation(schema.displayName!,
+ new AttributeTransformationModel(atmod, AggregateFunction.None),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count));
+ schemaDocuments.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
+ }
+ })));
+ return promises;
+ }, [] as Promise<void>[]));
+ return CurrentUserUtils._northstarSchemas.push(Docs.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! }));
+ });
+ }
+ }
+ public static set NorthstarDBCatalog(ctlog: Catalog | undefined) { this._northstarCatalog = ctlog; }
+
+ public static AddNorthstarSchema(schema: Schema, schemaDoc: Doc) {
+ if (this._northstarCatalog && CurrentUserUtils._northstarSchemas) {
+ this._northstarCatalog.schemas!.push(schema);
+ CurrentUserUtils._northstarSchemas.push(schemaDoc);
+ let schemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
+ schemas.push(schema.displayName!);
+ CurrentUserUtils.UserDocument.DBSchemas = new List<string>(schemas);
+ }
+ }
+ public static GetNorthstarSchema(name: string): Schema | undefined {
+ return !this._northstarCatalog || !this._northstarCatalog.schemas ? undefined :
+ ArrayUtil.FirstOrDefault<Schema>(this._northstarCatalog.schemas, (s: Schema) => s.displayName === name);
+ }
+ public static GetAllNorthstarColumnAttributes(schema: Schema) {
+ const recurs = (attrs: Attribute[], g?: AttributeGroup) => {
+ if (g && g.attributes) {
+ attrs.push.apply(attrs, g.attributes);
+ if (g.attributeGroups) {
+ g.attributeGroups.forEach(ng => recurs(attrs, ng));
+ }
+ }
+ return attrs;
+ };
+ return recurs([] as Attribute[], schema ? schema.rootAttributeGroup : undefined);
+ }
+} \ No newline at end of file
diff --git a/src/server/authentication/models/User.ts b/src/server/authentication/models/user_model.ts
index 9752c4260..ee85e1c05 100644
--- a/src/server/authentication/models/User.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -1,9 +1,8 @@
//@ts-ignore
import * as bcrypt from "bcrypt-nodejs";
-import * as crypto from "crypto";
//@ts-ignore
import * as mongoose from "mongoose";
-var url = 'mongodb://localhost:27017/Dash'
+var url = 'mongodb://localhost:27017/Dash';
mongoose.connect(url, { useNewUrlParser: true });
@@ -16,12 +15,13 @@ mongoose.connection.on('error', function (error) {
mongoose.connection.on('disconnected', function () {
console.log('connection closed');
});
-export type UserModel = mongoose.Document & {
+export type DashUserModel = mongoose.Document & {
email: string,
password: string,
- passwordResetToken: string,
- passwordResetExpires: Date,
- tokens: AuthToken[],
+ passwordResetToken?: string,
+ passwordResetExpires?: Date,
+
+ userDocumentId: string;
profile: {
name: string,
@@ -47,10 +47,11 @@ const userSchema = new mongoose.Schema({
passwordResetToken: String,
passwordResetExpires: Date,
+ userDocumentId: String,
+
facebook: String,
twitter: String,
google: String,
- tokens: Array,
profile: {
name: String,
@@ -65,22 +66,26 @@ const userSchema = new mongoose.Schema({
* Password hash middleware.
*/
userSchema.pre("save", function save(next) {
- const user = this as UserModel;
- if (!user.isModified("password")) { return next(); }
+ const user = this as DashUserModel;
+ if (!user.isModified("password")) {
+ return next();
+ }
bcrypt.genSalt(10, (err, salt) => {
- if (err) { return next(err); }
+ if (err) {
+ return next(err);
+ }
bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => {
- if (err) { return next(err); }
+ if (err) {
+ return next(err);
+ }
user.password = hash;
next();
});
});
});
-const comparePassword: comparePasswordFunction = function (this: UserModel, candidatePassword, cb) {
- bcrypt.compare(candidatePassword, this.password, (err: mongoose.Error, isMatch: boolean) => {
- cb(err, isMatch);
- });
+const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
+ bcrypt.compare(candidatePassword, this.password, cb);
};
userSchema.methods.comparePassword = comparePassword;
diff --git a/src/server/database.ts b/src/server/database.ts
index 07c5819ab..69005d2d3 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,81 +1,100 @@
-import { action, configure } from 'mobx';
import * as mongodb from 'mongodb';
-import { ObjectID } from 'mongodb';
import { Transferable } from './Message';
-import { Utils } from '../Utils';
export class Database {
- public static Instance = new Database()
+ public static DocumentsCollection = 'documents';
+ public static Instance = new Database();
private MongoClient = mongodb.MongoClient;
private url = 'mongodb://localhost:27017/Dash';
+ private currentWrites: { [id: string]: Promise<void> } = {};
private db?: mongodb.Db;
constructor() {
- this.MongoClient.connect(this.url, (err, client) => {
- this.db = client.db()
- })
+ this.MongoClient.connect(this.url, (err, client) => this.db = client.db());
}
- public update(id: string, value: any) {
+ public update(id: string, value: any, callback: () => void, upsert = true, collectionName = Database.DocumentsCollection) {
if (this.db) {
- let collection = this.db.collection('documents');
- collection.update({ _id: id }, { $set: value }, {
- upsert: true
- });
+ let collection = this.db.collection(collectionName);
+ const prom = this.currentWrites[id];
+ let newProm: Promise<void>;
+ const run = (): Promise<void> => {
+ return new Promise<void>(resolve => {
+ collection.updateOne({ _id: id }, value, { upsert }
+ , (err, res) => {
+ if (this.currentWrites[id] === newProm) {
+ delete this.currentWrites[id];
+ }
+ resolve();
+ callback();
+ });
+ });
+ };
+ newProm = prom ? prom.then(run) : run();
+ this.currentWrites[id] = newProm;
}
}
- public delete(id: string) {
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.remove({ _id: id });
- }
+ public delete(id: string, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).remove({ id: id });
}
- public deleteAll() {
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.deleteMany({});
- }
+ public deleteAll(collectionName = Database.DocumentsCollection): Promise<any> {
+ return new Promise(res =>
+ this.db && this.db.collection(collectionName).deleteMany({}, res));
}
- public insert(kvpairs: any) {
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.insertOne(kvpairs, (err: any, res: any) => {
- if (err) {
- // console.log(err)
- return
- }
- });
+ public insert(value: any, collectionName = Database.DocumentsCollection) {
+ if (!this.db) { return; }
+ if ("id" in value) {
+ value._id = value.id;
+ delete value.id;
}
+ const id = value._id;
+ const collection = this.db.collection(collectionName);
+ const prom = this.currentWrites[id];
+ let newProm: Promise<void>;
+ const run = (): Promise<void> => {
+ return new Promise<void>(resolve => {
+ collection.insertOne(value, (err, res) => {
+ if (this.currentWrites[id] === newProm) {
+ delete this.currentWrites[id];
+ }
+ resolve();
+ });
+ });
+ };
+ newProm = prom ? prom.then(run) : run();
+ this.currentWrites[id] = newProm;
}
- public getDocument(id: string, fn: (res: any) => void) {
- var result: JSON;
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.findOne({ _id: id }, (err: any, res: any) => {
- result = res
- if (!result) {
- fn(undefined)
- }
- fn(result)
- })
- };
+ public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).findOne({ _id: id }, (err, result) => {
+ if (result) {
+ result.id = result._id;
+ delete result._id;
+ fn(result);
+ } else {
+ fn(undefined);
+ }
+ });
}
- public getDocuments(ids: string[], fn: (res: any) => void) {
- if (this.db) {
- let collection = this.db.collection('documents');
- let cursor = collection.find({ _id: { "$in": ids } })
- cursor.toArray((err, docs) => {
- fn(docs);
- })
- };
+ public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => {
+ if (err) {
+ console.log(err.message);
+ console.log(err.errmsg);
+ }
+ fn(docs.map(doc => {
+ doc.id = doc._id;
+ delete doc._id;
+ return doc;
+ }));
+ });
}
public print() {
- console.log("db says hi!")
+ console.log("db says hi!");
}
}
diff --git a/src/server/index.ts b/src/server/index.ts
index 0d0b65b22..da6bc0165 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,68 +1,66 @@
-import * as express from 'express'
-const app = express()
-import * as webpack from 'webpack'
-import * as wdm from 'webpack-dev-middleware';
-import * as whm from 'webpack-hot-middleware';
-import * as path from 'path'
-import * as formidable from 'formidable'
+import * as bodyParser from 'body-parser';
+import { exec } from 'child_process';
+import * as cookieParser from 'cookie-parser';
+import * as express from 'express';
+import * as session from 'express-session';
+import * as expressValidator from 'express-validator';
+import * as formidable from 'formidable';
+import * as fs from 'fs';
+import * as mobileDetect from 'mobile-detect';
+import { ObservableMap } from 'mobx';
import * as passport from 'passport';
-import { MessageStore, Message, SetFieldArgs, GetFieldArgs, Transferable } from "./Message";
-import { Client } from './Client';
+import * as path from 'path';
+import * as request from 'request';
+import * as rp from 'request-promise';
+import * as io from 'socket.io';
import { Socket } from 'socket.io';
+import * as webpack from 'webpack';
+import * as wdm from 'webpack-dev-middleware';
+import * as whm from 'webpack-hot-middleware';
import { Utils } from '../Utils';
-import { ObservableMap } from 'mobx';
-import { FieldId, Field } from '../fields/Field';
+import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller';
+import { DashUserModel } from './authentication/models/user_model';
+import { Client } from './Client';
import { Database } from './database';
-import { ServerUtils } from './ServerUtil';
-import { ObjectID } from 'mongodb';
-import { Document } from '../fields/Document';
-import * as io from 'socket.io'
-import * as passportConfig from './authentication/config/passport';
-import { getLogin, postLogin, getSignup, postSignup } from './authentication/controllers/user';
+import { MessageStore, Transferable, Types, Diff } from "./Message";
+import { RouteStore } from './RouteStore';
+const app = express();
const config = require('../../webpack.config');
const compiler = webpack(config);
const port = 1050; // default port to listen
-const serverPort = 1234;
-import * as expressValidator from 'express-validator';
+const serverPort = 4321;
import expressFlash = require('express-flash');
-import * as bodyParser from 'body-parser';
-import * as session from 'express-session';
+import flash = require('connect-flash');
import c = require("crypto");
+import { Search } from './Search';
+import { debug } from 'util';
+import _ = require('lodash');
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
-const bluebird = require('bluebird');
-import { performance } from 'perf_hooks'
-import * as fs from 'fs';
-import * as request from 'request'
-const download = (url: string, dest: fs.PathLike) => {
- request.get(url).pipe(fs.createWriteStream(dest));
-}
+const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));
const mongoUrl = 'mongodb://localhost:27017/Dash';
-// mongoose.Promise = bluebird;
-mongoose.connect(mongoUrl)//.then(
-// () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },
-// ).catch((err: any) => {
-// console.log("MongoDB connection error. Please make sure MongoDB is running. " + err);
-// process.exit();
-// });
-mongoose.connection.on('connected', function () {
- console.log("connected");
-})
+mongoose.connect(mongoUrl);
+mongoose.connection.on('connected', () => console.log("connected"));
-app.use(bodyParser.json());
-app.use(bodyParser.urlencoded({ extended: true }));
-app.use(expressValidator());
-app.use(expressFlash());
-app.use(require('express-session')({
- secret: `${c.randomBytes(64)}`,
+// SESSION MANAGEMENT AND AUTHENTICATION MIDDLEWARE
+// ORDER OF IMPORTS MATTERS
+
+app.use(cookieParser());
+app.use(session({
+ secret: "64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc",
resave: true,
+ cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 },
saveUninitialized: true,
- store: new MongoStore({
- url: 'mongodb://localhost:27017/Dash'
- })
+ store: new MongoStore({ url: 'mongodb://localhost:27017/Dash' })
}));
+
+app.use(flash());
+app.use(expressFlash());
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: true }));
+app.use(expressValidator());
app.use(passport.initialize());
app.use(passport.session());
app.use((req, res, next) => {
@@ -70,111 +68,311 @@ app.use((req, res, next) => {
next();
});
-app.get("/signup", getSignup);
-app.post("/signup", postSignup);
-app.get("/login", getLogin);
-app.post("/login", postLogin);
-
-// IMAGE UPLOADING HANDLER
-app.post("/upload", (req, res, err) => {
- let form = new formidable.IncomingForm()
- form.uploadDir = __dirname + "/public/files/"
- form.keepExtensions = true
- // let path = req.body.path;
- console.log("upload")
- form.parse(req, (err, fields, files) => {
- console.log("parsing")
- let names: any[] = [];
- for (const name in files) {
- let file = files[name];
- names.push(`/files/` + path.basename(file.path));
+app.get("/hello", (req, res) => res.send("<p>Hello</p>"));
+
+enum Method {
+ GET,
+ POST
+}
+
+/**
+ * Please invoke this function when adding a new route to Dash's server.
+ * It ensures that any requests leading to or containing user-sensitive information
+ * does not execute unless Passport authentication detects a user logged in.
+ * @param method whether or not the request is a GET or a POST
+ * @param handler the action to invoke, recieving a DashUserModel and, as expected, the Express.Request and Express.Response
+ * @param onRejection an optional callback invoked on return if no user is found to be logged in
+ * @param subscribers the forward slash prepended path names (reference and add to RouteStore.ts) that will all invoke the given @param handler
+ */
+function addSecureRoute(method: Method,
+ handler: (user: DashUserModel, res: express.Response, req: express.Request) => void,
+ onRejection: (res: express.Response) => any = (res) => res.redirect(RouteStore.logout),
+ ...subscribers: string[]
+) {
+ let abstracted = (req: express.Request, res: express.Response) => {
+ if (req.user) {
+ handler(req.user, res, req);
+ } else {
+ onRejection(res);
+ }
+ };
+ subscribers.forEach(route => {
+ switch (method) {
+ case Method.GET:
+ app.get(route, abstracted);
+ break;
+ case Method.POST:
+ app.post(route, abstracted);
+ break;
}
- res.send(names);
});
-})
+}
-app.use(express.static(__dirname + '/public'));
-app.use('/images', express.static(__dirname + '/public'))
+// STATIC FILE SERVING
+app.use(express.static(__dirname + RouteStore.public));
+app.use(RouteStore.images, express.static(__dirname + RouteStore.public));
-let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
+app.get("/pull", (req, res) =>
+ exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', (err, stdout, stderr) => {
+ if (err) {
+ res.send(err.message);
+ return;
+ }
+ res.redirect("/");
+ }));
-// define a route handler for the default home page
-app.get("/", (req, res) => {
- res.sendFile(path.join(__dirname, '../../deploy/index.html'));
-});
+// SEARCH
-app.get("/hello", (req, res) => {
- res.send("<p>Hello</p>");
-})
+// GETTERS
-app.use("/corsProxy", (req, res) => {
- req.pipe(request(req.url.substring(1))).pipe(res);
+app.get("/search", async (req, res) => {
+ let query = req.query.query || "hello";
+ let results = await Search.Instance.search(query);
+ res.send(results);
});
-app.get("/delete", (req, res) => {
- deleteAll();
- res.redirect("/");
-});
+// anyone attempting to navigate to localhost at this port will
+// first have to login
+addSecureRoute(
+ Method.GET,
+ (user, res) => res.redirect(RouteStore.home),
+ undefined,
+ RouteStore.root
+);
+
+addSecureRoute(
+ Method.GET,
+ (user, res, req) => {
+ let detector = new mobileDetect(req.headers['user-agent'] || "");
+ let filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
+ res.sendFile(path.join(__dirname, '../../deploy/' + filename));
+ },
+ undefined,
+ RouteStore.home,
+ RouteStore.openDocumentWithId
+);
-app.use(wdm(compiler, {
- publicPath: config.output.publicPath
-}))
+addSecureRoute(
+ Method.GET,
+ (user, res) => res.send(user.userDocumentId || ""),
+ undefined,
+ RouteStore.getUserDocumentId,
+);
-app.use(whm(compiler))
+addSecureRoute(
+ Method.GET,
+ (user, res) => res.send(JSON.stringify({ id: user.id, email: user.email })),
+ undefined,
+ RouteStore.getCurrUser
+);
+
+// SETTERS
+
+addSecureRoute(
+ Method.POST,
+ (user, res, req) => {
+ let form = new formidable.IncomingForm();
+ form.uploadDir = __dirname + "/public/files/";
+ form.keepExtensions = true;
+ // let path = req.body.path;
+ console.log("upload");
+ form.parse(req, (err, fields, files) => {
+ console.log("parsing");
+ let names: string[] = [];
+ for (const name in files) {
+ names.push(`/files/` + path.basename(files[name].path));
+ }
+ res.send(names);
+ });
+ },
+ undefined,
+ RouteStore.upload
+);
+
+// AUTHENTICATION
+
+// Sign Up
+app.get(RouteStore.signup, getSignup);
+app.post(RouteStore.signup, postSignup);
+
+// Log In
+app.get(RouteStore.login, getLogin);
+app.post(RouteStore.login, postLogin);
+
+// Log Out
+app.get(RouteStore.logout, getLogout);
+
+// FORGOT PASSWORD EMAIL HANDLING
+app.get(RouteStore.forgot, getForgot);
+app.post(RouteStore.forgot, postForgot);
+
+// RESET PASSWORD EMAIL HANDLING
+app.get(RouteStore.reset, getReset);
+app.post(RouteStore.reset, postReset);
+
+app.use(RouteStore.corsProxy, (req, res) =>
+ req.pipe(request(req.url.substring(1))).pipe(res));
+
+app.get(RouteStore.delete, (req, res) =>
+ deleteFields().then(() => res.redirect(RouteStore.home)));
+
+app.get(RouteStore.deleteAll, (req, res) =>
+ deleteAll().then(() => res.redirect(RouteStore.home)));
+
+app.use(wdm(compiler, { publicPath: config.output.publicPath }));
+
+app.use(whm(compiler));
// start the Express server
-app.listen(port, () => {
- console.log(`server started at http://localhost:${port}`);
-})
+app.listen(port, () =>
+ console.log(`server started at http://localhost:${port}`));
const server = io();
interface Map {
[key: string]: Client;
}
-let clients: Map = {}
+let clients: Map = {};
server.on("connection", function (socket: Socket) {
- console.log("a user has connected")
+ console.log("a user has connected");
- Utils.Emit(socket, MessageStore.Foo, "handshooken")
+ Utils.Emit(socket, MessageStore.Foo, "handshooken");
- Utils.AddServerHandler(socket, MessageStore.Bar, barReceived)
- Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args))
- Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField)
- Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields)
- Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteAll)
-})
+ Utils.AddServerHandler(socket, MessageStore.Bar, barReceived);
+ Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
+ Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
-function deleteAll() {
- Database.Instance.deleteAll();
+ Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
+ Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
+});
+
+async function deleteFields() {
+ await Database.Instance.deleteAll();
+ await Search.Instance.clear();
+ await Database.Instance.deleteAll('newDocuments');
+}
+
+async function deleteAll() {
+ await Database.Instance.deleteAll();
+ await Database.Instance.deleteAll('newDocuments');
+ await Database.Instance.deleteAll('sessions');
+ await Database.Instance.deleteAll('users');
+ await Search.Instance.clear();
}
function barReceived(guid: String) {
clients[guid.toString()] = new Client(guid.toString());
}
-function addDocument(document: Document) {
+function getField([id, callback]: [string, (result?: Transferable) => void]) {
+ Database.Instance.getDocument(id, (result?: Transferable) =>
+ callback(result ? result : undefined));
+}
+function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
+ Database.Instance.getDocuments(ids, callback);
}
-function getField([id, callback]: [string, (result: any) => void]) {
- Database.Instance.getDocument(id, (result: any) => {
- if (result) {
- callback(result)
+function setField(socket: Socket, newValue: Transferable) {
+ Database.Instance.update(newValue.id, newValue, () =>
+ socket.broadcast.emit(MessageStore.SetField.Message, newValue));
+ if (newValue.type === Types.Text) {
+ Search.Instance.updateDocument({ id: newValue.id, data: (newValue as any).data });
+ console.log("set field");
+ }
+}
+
+function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
+ Database.Instance.getDocument(id, callback, "newDocuments");
+}
+
+function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) {
+ Database.Instance.getDocuments(ids, callback, "newDocuments");
+}
+
+
+const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
+ "number": "_n",
+ "string": "_t",
+ // "boolean": "_b",
+ "image": ["_t", "url"],
+ "video": ["_t", "url"],
+ "pdf": ["_t", "url"],
+ "audio": ["_t", "url"],
+ "web": ["_t", "url"],
+ "date": ["_d", value => new Date(value.date).toISOString()],
+ "proxy": ["_i", "fieldId"],
+ "list": ["_l", list => {
+ const results = [];
+ for (const value of list.fields) {
+ const term = ToSearchTerm(value);
+ if (term) {
+ results.push(term.value);
+ }
}
- else {
- callback(undefined)
+ return results.length ? results : null;
+ }]
+};
+
+function ToSearchTerm(val: any): { suffix: string, value: any } | undefined {
+ if (val === null || val === undefined) {
+ return;
+ }
+ const type = val.__type || typeof val;
+ let suffix = suffixMap[type];
+ if (!suffix) {
+ return;
+ }
+
+ if (Array.isArray(suffix)) {
+ const accessor = suffix[1];
+ if (typeof accessor === "function") {
+ val = accessor(val);
+ } else {
+ val = val[accessor];
}
- })
+ suffix = suffix[0];
+ }
+
+ return { suffix, value: val };
}
-function getFields([ids, callback]: [string[], (result: any) => void]) {
- Database.Instance.getDocuments(ids, callback);
+function getSuffix(value: string | [string, any]): string {
+ return typeof value === "string" ? value : value[0];
}
-function setField(socket: Socket, newValue: Transferable) {
- Database.Instance.update(newValue._id, newValue)
- socket.broadcast.emit(MessageStore.SetField.Message, newValue)
+function UpdateField(socket: Socket, diff: Diff) {
+ Database.Instance.update(diff.id, diff.diff,
+ () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments");
+ const docfield = diff.diff.$set;
+ if (!docfield) {
+ return;
+ }
+ const update: any = { id: diff.id };
+ let dynfield = false;
+ for (let key in docfield) {
+ if (!key.startsWith("fields.")) continue;
+ dynfield = true;
+ let val = docfield[key];
+ key = key.substring(7);
+ Object.values(suffixMap).forEach(suf => update[key + getSuffix(suf)] = { set: null });
+ let term = ToSearchTerm(val);
+ if (term !== undefined) {
+ let { suffix, value } = term;
+ update[key + suffix] = { set: value };
+ }
+ }
+ if (dynfield) {
+ Search.Instance.updateDocument(update);
+ }
+}
+
+function CreateField(newValue: any) {
+ Database.Instance.insert(newValue, "newDocuments");
}
server.listen(serverPort);
diff --git a/src/server/public/files/.gitignore b/src/server/public/files/.gitignore
new file mode 100644
index 000000000..f59ec20aa
--- /dev/null
+++ b/src/server/public/files/.gitignore
@@ -0,0 +1 @@
+* \ No newline at end of file
diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts
index e4a66f7f2..7939ae8be 100644
--- a/src/typings/index.d.ts
+++ b/src/typings/index.d.ts
@@ -1,322 +1,322 @@
/// <reference types="node" />
declare module '@react-pdf/renderer' {
- import * as React from 'react';
-
- namespace ReactPDF {
- interface Style {
- [property: string]: any;
- }
- interface Styles {
- [key: string]: Style;
- }
- type Orientation = 'portrait' | 'landscape';
-
- interface DocumentProps {
- title?: string;
- author?: string;
- subject?: string;
- keywords?: string;
- creator?: string;
- producer?: string;
- onRender?: () => any;
- }
-
- /**
- * This component represent the PDF document itself. It must be the root
- * of your tree element structure, and under no circumstances should it be
- * used as children of another react-pdf component. In addition, it should
- * only have childs of type <Page />.
- */
- class Document extends React.Component<DocumentProps> {}
-
- interface NodeProps {
- style?: Style | Style[];
- /**
- * Render component in all wrapped pages.
- * @see https://react-pdf.org/advanced#fixed-components
- */
- fixed?: boolean;
- /**
- * Force the wrapping algorithm to start a new page when rendering the
- * element.
- * @see https://react-pdf.org/advanced#page-breaks
- */
- break?: boolean;
- }
-
- interface PageProps extends NodeProps {
- /**
- * Enable page wrapping for this page.
- * @see https://react-pdf.org/components#page-wrapping
- */
- wrap?: boolean;
- debug?: boolean;
- size?: string | [number, number] | {width: number; height: number};
- orientation?: Orientation;
- ruler?: boolean;
- rulerSteps?: number;
- verticalRuler?: boolean;
- verticalRulerSteps?: number;
- horizontalRuler?: boolean;
- horizontalRulerSteps?: number;
- ref?: Page;
- }
-
- /**
- * Represents single page inside the PDF document, or a subset of them if
- * using the wrapping feature. A <Document /> can contain as many pages as
- * you want, but ensure not rendering a page inside any component besides
- * Document.
- */
- class Page extends React.Component<PageProps> {}
-
- interface ViewProps extends NodeProps {
- /**
- * Enable/disable page wrapping for element.
- * @see https://react-pdf.org/components#page-wrapping
- */
- wrap?: boolean;
- debug?: boolean;
- render?: (props: {pageNumber: number}) => React.ReactNode;
- children?: React.ReactNode;
- }
-
+ import * as React from 'react';
+
+ namespace ReactPDF {
+ interface Style {
+ [property: string]: any;
+ }
+ interface Styles {
+ [key: string]: Style;
+ }
+ type Orientation = 'portrait' | 'landscape';
+
+ interface DocumentProps {
+ title?: string;
+ author?: string;
+ subject?: string;
+ keywords?: string;
+ creator?: string;
+ producer?: string;
+ onRender?: () => any;
+ }
+
+ /**
+ * This component represent the PDF document itself. It must be the root
+ * of your tree element structure, and under no circumstances should it be
+ * used as children of another react-pdf component. In addition, it should
+ * only have childs of type <Page />.
+ */
+ class Document extends React.Component<DocumentProps> { }
+
+ interface NodeProps {
+ style?: Style | Style[];
/**
- * The most fundamental component for building a UI and is designed to be
- * nested inside other views and can have 0 to many children.
+ * Render component in all wrapped pages.
+ * @see https://react-pdf.org/advanced#fixed-components
*/
- class View extends React.Component<ViewProps> {}
-
- interface ImageProps extends NodeProps {
- debug?: boolean;
- src: string | {data: Buffer; format: 'png' | 'jpg'};
- cache?: boolean;
- }
-
+ fixed?: boolean;
/**
- * A React component for displaying network or local (Node only) JPG or
- * PNG images, as well as base64 encoded image strings.
+ * Force the wrapping algorithm to start a new page when rendering the
+ * element.
+ * @see https://react-pdf.org/advanced#page-breaks
*/
- class Image extends React.Component<ImageProps> {}
-
- interface TextProps extends NodeProps {
- /**
- * Enable/disable page wrapping for element.
- * @see https://react-pdf.org/components#page-wrapping
- */
- wrap?: boolean;
- debug?: boolean;
- render?: (
- props: {pageNumber: number; totalPages: number},
- ) => React.ReactNode;
- children?: React.ReactNode;
- /**
- * How much hyphenated breaks should be avoided.
- */
- hyphenationCallback?: number;
- }
-
+ break?: boolean;
+ }
+
+ interface PageProps extends NodeProps {
/**
- * A React component for displaying text. Text supports nesting of other
- * Text or Link components to create inline styling.
+ * Enable page wrapping for this page.
+ * @see https://react-pdf.org/components#page-wrapping
*/
- class Text extends React.Component<TextProps> {}
-
- interface LinkProps extends NodeProps {
- /**
- * Enable/disable page wrapping for element.
- * @see https://react-pdf.org/components#page-wrapping
- */
- wrap?: boolean;
- debug?: boolean;
- src: string;
- children?: React.ReactNode;
- }
-
+ wrap?: boolean;
+ debug?: boolean;
+ size?: string | [number, number] | { width: number; height: number };
+ orientation?: Orientation;
+ ruler?: boolean;
+ rulerSteps?: number;
+ verticalRuler?: boolean;
+ verticalRulerSteps?: number;
+ horizontalRuler?: boolean;
+ horizontalRulerSteps?: number;
+ ref?: Page;
+ }
+
+ /**
+ * Represents single page inside the PDF document, or a subset of them if
+ * using the wrapping feature. A <Document /> can contain as many pages as
+ * you want, but ensure not rendering a page inside any component besides
+ * Document.
+ */
+ class Page extends React.Component<PageProps> { }
+
+ interface ViewProps extends NodeProps {
/**
- * A React component for displaying an hyperlink. Link’s can be nested
- * inside a Text component, or being inside any other valid primitive.
+ * Enable/disable page wrapping for element.
+ * @see https://react-pdf.org/components#page-wrapping
*/
- class Link extends React.Component<LinkProps> {}
-
- interface NoteProps extends NodeProps {
- children: string;
- }
-
- class Note extends React.Component<NoteProps> {}
-
- interface BlobProviderParams {
- blob: Blob | null;
- url: string | null;
- loading: boolean;
- error: Error | null;
- }
- interface BlobProviderProps {
- document: React.ReactElement<DocumentProps>;
- children: (params: BlobProviderParams) => React.ReactNode;
- }
-
+ wrap?: boolean;
+ debug?: boolean;
+ render?: (props: { pageNumber: number }) => React.ReactNode;
+ children?: React.ReactNode;
+ }
+
+ /**
+ * The most fundamental component for building a UI and is designed to be
+ * nested inside other views and can have 0 to many children.
+ */
+ class View extends React.Component<ViewProps> { }
+
+ interface ImageProps extends NodeProps {
+ debug?: boolean;
+ src: string | { data: Buffer; format: 'png' | 'jpg' };
+ cache?: boolean;
+ }
+
+ /**
+ * A React component for displaying network or local (Node only) JPG or
+ * PNG images, as well as base64 encoded image strings.
+ */
+ class Image extends React.Component<ImageProps> { }
+
+ interface TextProps extends NodeProps {
/**
- * Easy and declarative way of getting document's blob data without
- * showing it on screen.
- * @see https://react-pdf.org/advanced#on-the-fly-rendering
- * @platform web
+ * Enable/disable page wrapping for element.
+ * @see https://react-pdf.org/components#page-wrapping
*/
- class BlobProvider extends React.Component<BlobProviderProps> {}
-
- interface PDFViewerProps {
- width?: number;
- height?: number;
- style?: Style | Style[];
- className?: string;
- children?: React.ReactElement<DocumentProps>;
- }
-
+ wrap?: boolean;
+ debug?: boolean;
+ render?: (
+ props: { pageNumber: number; totalPages: number },
+ ) => React.ReactNode;
+ children?: React.ReactNode;
/**
- * Iframe PDF viewer for client-side generated documents.
- * @platform web
+ * How much hyphenated breaks should be avoided.
*/
- class PDFViewer extends React.Component<PDFViewerProps> {}
-
- interface PDFDownloadLinkProps {
- document: React.ReactElement<DocumentProps>;
- fileName?: string;
- style?: Style | Style[];
- className?: string;
- children?:
- | React.ReactNode
- | ((params: BlobProviderParams) => React.ReactNode);
- }
-
+ hyphenationCallback?: number;
+ }
+
+ /**
+ * A React component for displaying text. Text supports nesting of other
+ * Text or Link components to create inline styling.
+ */
+ class Text extends React.Component<TextProps> { }
+
+ interface LinkProps extends NodeProps {
/**
- * Anchor tag to enable generate and download PDF documents on the fly.
- * @see https://react-pdf.org/advanced#on-the-fly-rendering
- * @platform web
+ * Enable/disable page wrapping for element.
+ * @see https://react-pdf.org/components#page-wrapping
*/
- class PDFDownloadLink extends React.Component<PDFDownloadLinkProps> {}
-
- interface EmojiSource {
- url: string;
- format: string;
- }
- interface RegisteredFont {
- src: string;
- loaded: boolean;
- loading: boolean;
- data: any;
- [key: string]: any;
- }
- type HyphenationCallback = (
- words: string[],
- glyphString: {[key: string]: any},
- ) => string[];
-
- const Font: {
- register: (
- src: string,
- options: {family: string; [key: string]: any},
- ) => void;
- getEmojiSource: () => EmojiSource;
- getRegisteredFonts: () => string[];
- registerEmojiSource: (emojiSource: EmojiSource) => void;
- registerHyphenationCallback: (
- hyphenationCallback: HyphenationCallback,
- ) => void;
- getHyphenationCallback: () => HyphenationCallback;
- getFont: (fontFamily: string) => RegisteredFont | undefined;
- load: (
- fontFamily: string,
- document: React.ReactElement<DocumentProps>,
- ) => Promise<void>;
- clear: () => void;
- reset: () => void;
- };
-
- const StyleSheet: {
- hairlineWidth: number;
- create: <TStyles>(styles: TStyles) => TStyles;
- resolve: (
- style: Style,
- container: {
- width: number;
- height: number;
- orientation: Orientation;
- },
- ) => Style;
- flatten: (...styles: Style[]) => Style;
- absoluteFillObject: {
- position: 'absolute';
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- };
- };
-
- const version: any;
-
- const PDFRenderer: any;
-
- const createInstance: (
- element: {
- type: string;
- props: {[key: string]: any};
- },
- root?: any,
- ) => any;
-
- const pdf: (
+ wrap?: boolean;
+ debug?: boolean;
+ src: string;
+ children?: React.ReactNode;
+ }
+
+ /**
+ * A React component for displaying an hyperlink. Link’s can be nested
+ * inside a Text component, or being inside any other valid primitive.
+ */
+ class Link extends React.Component<LinkProps> { }
+
+ interface NoteProps extends NodeProps {
+ children: string;
+ }
+
+ class Note extends React.Component<NoteProps> { }
+
+ interface BlobProviderParams {
+ blob: Blob | null;
+ url: string | null;
+ loading: boolean;
+ error: Error | null;
+ }
+ interface BlobProviderProps {
+ document: React.ReactElement<DocumentProps>;
+ children: (params: BlobProviderParams) => React.ReactNode;
+ }
+
+ /**
+ * Easy and declarative way of getting document's blob data without
+ * showing it on screen.
+ * @see https://react-pdf.org/advanced#on-the-fly-rendering
+ * @platform web
+ */
+ class BlobProvider extends React.Component<BlobProviderProps> { }
+
+ interface PDFViewerProps {
+ width?: number;
+ height?: number;
+ style?: Style | Style[];
+ className?: string;
+ children?: React.ReactElement<DocumentProps>;
+ }
+
+ /**
+ * Iframe PDF viewer for client-side generated documents.
+ * @platform web
+ */
+ class PDFViewer extends React.Component<PDFViewerProps> { }
+
+ interface PDFDownloadLinkProps {
+ document: React.ReactElement<DocumentProps>;
+ fileName?: string;
+ style?: Style | Style[];
+ className?: string;
+ children?:
+ | React.ReactNode
+ | ((params: BlobProviderParams) => React.ReactNode);
+ }
+
+ /**
+ * Anchor tag to enable generate and download PDF documents on the fly.
+ * @see https://react-pdf.org/advanced#on-the-fly-rendering
+ * @platform web
+ */
+ class PDFDownloadLink extends React.Component<PDFDownloadLinkProps> { }
+
+ interface EmojiSource {
+ url: string;
+ format: string;
+ }
+ interface RegisteredFont {
+ src: string;
+ loaded: boolean;
+ loading: boolean;
+ data: any;
+ [key: string]: any;
+ }
+ type HyphenationCallback = (
+ words: string[],
+ glyphString: { [key: string]: any },
+ ) => string[];
+
+ const Font: {
+ register: (
+ src: string,
+ options: { family: string;[key: string]: any },
+ ) => void;
+ getEmojiSource: () => EmojiSource;
+ getRegisteredFonts: () => string[];
+ registerEmojiSource: (emojiSource: EmojiSource) => void;
+ registerHyphenationCallback: (
+ hyphenationCallback: HyphenationCallback,
+ ) => void;
+ getHyphenationCallback: () => HyphenationCallback;
+ getFont: (fontFamily: string) => RegisteredFont | undefined;
+ load: (
+ fontFamily: string,
document: React.ReactElement<DocumentProps>,
- ) => {
- isDirty: () => boolean;
- updateContainer: (document: React.ReactElement<any>) => void;
- toBuffer: () => NodeJS.ReadableStream;
- toBlob: () => Blob;
- toString: () => string;
+ ) => Promise<void>;
+ clear: () => void;
+ reset: () => void;
+ };
+
+ const StyleSheet: {
+ hairlineWidth: number;
+ create: <TStyles>(styles: TStyles) => TStyles;
+ resolve: (
+ style: Style,
+ container: {
+ width: number;
+ height: number;
+ orientation: Orientation;
+ },
+ ) => Style;
+ flatten: (...styles: Style[]) => Style;
+ absoluteFillObject: {
+ position: 'absolute';
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
};
-
- const renderToStream: (
- document: React.ReactElement<DocumentProps>,
- ) => NodeJS.ReadableStream;
-
- const renderToFile: (
- document: React.ReactElement<DocumentProps>,
- filePath: string,
- callback?: (output: NodeJS.ReadableStream, filePath: string) => any,
- ) => Promise<NodeJS.ReadableStream>;
-
- const render: typeof renderToFile;
- }
-
- const Document: typeof ReactPDF.Document;
- const Page: typeof ReactPDF.Page;
- const View: typeof ReactPDF.View;
- const Image: typeof ReactPDF.Image;
- const Text: typeof ReactPDF.Text;
- const Link: typeof ReactPDF.Link;
- const Note: typeof ReactPDF.Note;
- const Font: typeof ReactPDF.Font;
- const StyleSheet: typeof ReactPDF.StyleSheet;
- const createInstance: typeof ReactPDF.createInstance;
- const PDFRenderer: typeof ReactPDF.PDFRenderer;
- const version: typeof ReactPDF.version;
- const pdf: typeof ReactPDF.pdf;
-
- export default ReactPDF;
- export {
- Document,
- Page,
- View,
- Image,
- Text,
- Link,
- Note,
- Font,
- StyleSheet,
- createInstance,
- PDFRenderer,
- version,
- pdf,
};
- } \ No newline at end of file
+
+ const version: any;
+
+ const PDFRenderer: any;
+
+ const createInstance: (
+ element: {
+ type: string;
+ props: { [key: string]: any };
+ },
+ root?: any,
+ ) => any;
+
+ const pdf: (
+ document: React.ReactElement<DocumentProps>,
+ ) => {
+ isDirty: () => boolean;
+ updateContainer: (document: React.ReactElement<any>) => void;
+ toBuffer: () => NodeJS.ReadableStream;
+ toBlob: () => Blob;
+ toString: () => string;
+ };
+
+ const renderToStream: (
+ document: React.ReactElement<DocumentProps>,
+ ) => NodeJS.ReadableStream;
+
+ const renderToFile: (
+ document: React.ReactElement<DocumentProps>,
+ filePath: string,
+ callback?: (output: NodeJS.ReadableStream, filePath: string) => any,
+ ) => Promise<NodeJS.ReadableStream>;
+
+ const render: typeof renderToFile;
+ }
+
+ const Document: typeof ReactPDF.Document;
+ const Page: typeof ReactPDF.Page;
+ const View: typeof ReactPDF.View;
+ const Image: typeof ReactPDF.Image;
+ const Text: typeof ReactPDF.Text;
+ const Link: typeof ReactPDF.Link;
+ const Note: typeof ReactPDF.Note;
+ const Font: typeof ReactPDF.Font;
+ const StyleSheet: typeof ReactPDF.StyleSheet;
+ const createInstance: typeof ReactPDF.createInstance;
+ const PDFRenderer: typeof ReactPDF.PDFRenderer;
+ const version: typeof ReactPDF.version;
+ const pdf: typeof ReactPDF.pdf;
+
+ export default ReactPDF;
+ export {
+ Document,
+ Page,
+ View,
+ Image,
+ Text,
+ Link,
+ Note,
+ Font,
+ StyleSheet,
+ createInstance,
+ PDFRenderer,
+ version,
+ pdf,
+ };
+} \ No newline at end of file
diff --git a/test/test.ts b/test/test.ts
index 0fa1ea15b..91dc43379 100644
--- a/test/test.ts
+++ b/test/test.ts
@@ -1,171 +1,35 @@
-import { NumberField } from "../src/fields/NumberField";
import { expect } from 'chai';
-import 'mocha'
-import { Key } from "../src/fields/Key";
-import { Document } from "../src/fields/Document";
+import 'mocha';
import { autorun, reaction } from "mobx";
-import { DocumentReference } from "../src/fields/DocumentReference";
-import { TextField } from "../src/fields/TextField";
-import { Field, FieldWaiting } from "../src/fields/Field";
-
-describe('Number Controller', () => {
- it('Should be constructable', () => {
- const numController = new NumberField(15);
- expect(numController.Data).to.equal(15);
- });
-
- it('Should update', () => {
- const numController = new NumberField(15);
- let ran = false;
- reaction(() => numController.Data, (data) => { ran = true; })
- expect(ran).to.equal(false);
- numController.Data = 5;
- expect(ran).to.equal(true);
- });
-});
+import { Doc } from '../src/new_fields/Doc';
+import { Cast } from '../src/new_fields/Types';
describe("Document", () => {
it('should hold fields', () => {
- let key = new Key("Test");
- let key2 = new Key("Test2");
- let field = new NumberField(15);
- let doc = new Document();
- doc.Set(key, field);
- let getField = doc.GetT(key, NumberField);
- let getField2 = doc.GetT(key2, NumberField);
+ let key = "Test";
+ let key2 = "Test2";
+ let field = 15;
+ let doc = new Doc();
+ doc[key] = field;
+ let getField = Cast(doc[key], "number");
+ let getField2 = Cast(doc[key2], "number");
expect(getField).to.equal(field);
expect(getField2).to.equal(undefined);
});
it('should update', () => {
- let doc = new Document();
- let key = new Key("Test");
- let key2 = new Key("Test2");
+ let doc = new Doc();
+ let key = "Test";
+ let key2 = "Test2";
let ran = false;
- reaction(() => doc.Get(key), (field) => { ran = true });
+ reaction(() => doc[key], (field) => { ran = true; });
expect(ran).to.equal(false);
- doc.Set(key2, new NumberField(4));
+ doc[key2] = 4;
expect(ran).to.equal(false);
- doc.Set(key, new NumberField(5));
+ doc[key] = 5;
expect(ran).to.equal(true);
});
});
-
-describe("Reference", () => {
- it('should dereference', () => {
- let doc = new Document();
- let doc2 = new Document();
- const key = new Key("test");
- const key2 = new Key("test2");
-
- const numCont = new NumberField(55);
- doc.Set(key, numCont);
- let ref = new DocumentReference(doc, key);
- let ref2 = new DocumentReference(doc, key2);
- doc2.Set(key2, ref);
-
- let ref3 = new DocumentReference(doc2, key2);
- let ref4 = new DocumentReference(doc2, key);
-
- expect(ref.Dereference()).to.equal(numCont);
- expect(ref.DereferenceToRoot()).to.equal(numCont);
- expect(ref2.Dereference()).to.equal(undefined);
- expect(ref2.DereferenceToRoot()).to.equal(undefined);
- expect(ref3.Dereference()).to.equal(ref);
- expect(ref3.DereferenceToRoot()).to.equal(numCont);
- expect(ref4.Dereference()).to.equal(undefined);
- expect(ref4.DereferenceToRoot()).to.equal(undefined);
- });
-
- it('should work with prototypes', () => {
- let doc = new Document;
- let doc2 = doc.MakeDelegate();
- let key = new Key("test");
- expect(doc.Get(key)).to.equal(undefined);
- expect(doc2.Get(key)).to.equal(undefined);
- let num = new NumberField(55);
- let num2 = new NumberField(56);
-
- doc.Set(key, num);
- expect(doc.Get(key)).to.equal(num);
- expect(doc2.Get(key)).to.equal(num);
-
- doc2.Set(key, num2);
- expect(doc.Get(key)).to.equal(num);
- expect(doc2.Get(key)).to.equal(num2);
- });
-
- it('should update through layers', () => {
- let doc = new Document();
- let doc2 = new Document();
- let doc3 = new Document();
- const key = new Key("test");
- const key2 = new Key("test2");
- const key3 = new Key("test3");
-
- const numCont = new NumberField(55);
- doc.Set(key, numCont);
- const ref = new DocumentReference(doc, key);
- doc2.Set(key2, ref);
- const ref3 = new DocumentReference(doc2, key2);
- doc3.Set(key3, ref3);
-
- let ran = false;
- reaction(() => {
- let field = (<Field>(<Field>doc3.Get(key3)).DereferenceToRoot()).GetValue();
- return field;
- }, (field) => {
- ran = true;
- });
- expect(ran).to.equal(false);
-
- numCont.Data = 44;
- expect(ran).to.equal(true);
- ran = false;
-
- doc.Set(key, new NumberField(33));
- expect(ran).to.equal(true);
- ran = false;
-
- doc.Set(key2, new NumberField(4));
- expect(ran).to.equal(false);
-
- doc2.Set(key2, new TextField("hello"));
- expect(ran).to.equal(true);
- ran = false;
-
- doc3.Set(key3, new TextField("world"));
- expect(ran).to.equal(true);
- ran = false;
- });
-
- it('should update with prototypes', () => {
- let doc = new Document();
- let doc2 = doc.MakeDelegate();
- const key = new Key("test");
-
- const numCont = new NumberField(55);
-
- let ran = false;
- reaction(() => {
- let field = doc2.GetT(key, NumberField);
- if (field && field != FieldWaiting) {
- return field.Data;
- }
- return undefined;
- }, (field) => {
- ran = true;
- });
- expect(ran).to.equal(false);
-
- doc.Set(key, numCont);
- expect(ran).to.equal(true);
-
- ran = false;
- numCont.Data = 1;
- expect(ran).to.equal(true);
- });
-}); \ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 2b2c69fe1..0d4d77002 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,23 +1,23 @@
{
- "compilerOptions": {
- "target": "es5",
- "removeComments": true,
- "experimentalDecorators": true,
- "strict": true,
- "jsx": "react",
- "sourceMap": true,
- "outDir": "dist",
- "lib": [
- "dom",
- "es2015"
+ "compilerOptions": {
+ "target": "es5",
+ "removeComments": true,
+ "experimentalDecorators": true,
+ "strict": true,
+ "jsx": "react",
+ "sourceMap": true,
+ "outDir": "dist",
+ "lib": [
+ "dom",
+ "es2015"
+ ],
+ },
+ // "exclude": [
+ // "node_modules",
+ // "static"
+ // ],
+ "typeRoots": [
+ "./node_modules/@types",
+ "./src/typings"
]
- },
- // "exclude": [
- // "node_modules",
- // "static"
- // ],
- "typeRoots": [
- "./node_modules/@types",
- "./src/typings"
- ]
} \ No newline at end of file
diff --git a/tslint.json b/tslint.json
new file mode 100644
index 000000000..76d28b375
--- /dev/null
+++ b/tslint.json
@@ -0,0 +1,57 @@
+{
+ "rules": {
+ // "no-non-null-assertion": true,
+ "no-return-await": true,
+ "no-string-literal": true,
+ // "no-var-keyword": true,
+ // "no-var-requires": true,
+ "prefer-object-spread": true,
+ "prefer-for-of": true,
+ "no-unnecessary-type-assertion": true,
+ // "no-void-expression": [
+ // true,
+ // "ignore-arrow-function-shorthand"
+ // ],
+ "triple-equals": true,
+ // "prefer-const": true,
+ "no-unnecessary-callback-wrapper": true,
+ // "align": [
+ // true,
+ // "parameters",
+ // "arguments",
+ // "statements",
+ // "members",
+ // "elements"
+ // ],
+ "class-name": true,
+ "arrow-return-shorthand": true,
+ // "object-literal-shorthand": true,
+ // "object-literal-sort-keys": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "curly": [
+ true,
+ "ignore-same-line"
+ ],
+ // "quotemark": [
+ // true,
+ // "double",
+ // "jsx-double",
+ // "avoid-template",
+ // "avoid-escape"
+ // ],
+ "no-tautology-expression": true,
+ "unnecessary-constructor": true
+ // "trailing-comma": [
+ // true,
+ // {
+ // "multiline": "always",
+ // "singleline": "never"
+ // }
+ // ],
+ // "ordered-imports": true
+ },
+ "defaultSeverity": "warning"
+} \ No newline at end of file
diff --git a/views/forgot.pug b/views/forgot.pug
new file mode 100644
index 000000000..4036b49db
--- /dev/null
+++ b/views/forgot.pug
@@ -0,0 +1,22 @@
+
+extends ./layout
+
+block content
+ style
+ include ./stylesheets/authentication.css
+ form.form-horizontal(id='forgot-form', method='POST')
+ input(type='hidden', name='_csrf', value=_csrf)
+ .overlay(id='overlay_forgot')
+ a(href="/login")
+ img(id='to_login', src="https://bit.ly/2U6ouZk", alt="")
+ .inner.forgot
+ h3.auth_header Recover Password
+ .form-group
+ //- label.col-sm-3.control-label(for='email', id='email_label') Email
+ .col-sm-7
+ input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus, required)
+ .form-group
+ .col-sm-offset-3.col-sm-7
+ button.btn.btn-success(id='submit', type='submit')
+ i.fa.fa-user-plus
+ | Submit \ No newline at end of file
diff --git a/views/layout.pug b/views/layout.pug
index fb22ae770..b11291eb9 100644
--- a/views/layout.pug
+++ b/views/layout.pug
@@ -8,10 +8,7 @@ html(lang='')
meta(name='description', content='')
meta(name='theme-color' content='#4DA5F4')
meta(name='csrf-token', content=_csrf)
- link(rel='shortcut icon', href='/images/favicon.png')
link(rel='stylesheet', href='/css/main.css')
body
-
- .container
- block content \ No newline at end of file
+ block content \ No newline at end of file
diff --git a/views/login.pug b/views/login.pug
new file mode 100644
index 000000000..9bc40a495
--- /dev/null
+++ b/views/login.pug
@@ -0,0 +1,28 @@
+
+extends ./layout
+
+block content
+ style
+ include ./stylesheets/authentication.css
+ form.form-horizontal(id='login-form', method='POST')
+ input(type='hidden', name='_csrf', value=_csrf)
+ .overlay(id='overlay_login')
+ a(href="/signup")
+ img(id='new_user', src="https://bit.ly/2EuqPb4", alt="")
+ a(href="/forgot")
+ img(id='forgot', src="https://bit.ly/2XjHpSo", alt="")
+ .inner.login
+ h3.auth_header Log In
+ .form-group
+ //- label.col-sm-3.control-label(for='email', id='email_label') Email
+ .col-sm-7
+ input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus, required)
+ .form-group
+ //- label.col-sm-3.control-label(for='password') Password
+ .col-sm-7
+ input.form-control(type='password', name='password', id='password', placeholder='Password', required)
+ .form-group
+ .col-sm-offset-3.col-sm-7
+ button.btn.btn-success(id='submit', type='submit')
+ i.fa.fa-user-plus
+ | Submit \ No newline at end of file
diff --git a/views/reset.pug b/views/reset.pug
new file mode 100644
index 000000000..8b6fa952b
--- /dev/null
+++ b/views/reset.pug
@@ -0,0 +1,22 @@
+
+extends ./layout
+
+block content
+ style
+ include ./stylesheets/authentication.css
+ form.form-horizontal(id='reset-form', method='POST')
+ input(type='hidden', name='_csrf', value=_csrf)
+ .overlay(id='overlay_reset')
+ .inner.reset
+ h3.auth_header Reset Password
+ .form-group
+ .col-sm-7
+ input.form-control(type='password', name='password', id='password', placeholder='Password', required)
+ .form-group
+ .col-sm-7
+ input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password', required)
+ .form-group
+ .col-sm-offset-3.col-sm-7
+ button.btn.btn-success(type='submit')
+ i.fa.fa-user-plus
+ | Reset \ No newline at end of file
diff --git a/views/resources/dashlogo.png b/views/resources/dashlogo.png
new file mode 100644
index 000000000..3ba4e111b
--- /dev/null
+++ b/views/resources/dashlogo.png
Binary files differ
diff --git a/views/signup.pug b/views/signup.pug
index a23f334af..11b02a5eb 100644
--- a/views/signup.pug
+++ b/views/signup.pug
@@ -2,24 +2,26 @@
extends ./layout
block content
- .page-header
- h3 Sign up
- form.form-horizontal(id='signup-form', method='POST')
- input(type='hidden', name='_csrf', value=_csrf)
- .form-group
- label.col-sm-3.control-label(for='email') Email
- .col-sm-7
- input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus, required)
- .form-group
- label.col-sm-3.control-label(for='password') Password
- .col-sm-7
- input.form-control(type='password', name='password', id='password', placeholder='Password', required)
- .form-group
- label.col-sm-3.control-label(for='confirmPassword') Confirm Password
- .col-sm-7
- input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password', required)
- .form-group
- .col-sm-offset-3.col-sm-7
- button.btn.btn-success(type='submit')
- i.fa.fa-user-plus
- | Signup \ No newline at end of file
+ style
+ include ./stylesheets/authentication.css
+ form.form-horizontal(id='signup-form', method='POST')
+ input(type='hidden', name='_csrf', value=_csrf)
+ .overlay(id='overlay_signup')
+ a(href="/login")
+ img(id='to_login', src="https://bit.ly/2U6ouZk", alt="")
+ .inner.signup
+ h3.auth_header Create An Account
+ .form-group
+ .col-sm-7
+ input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus, required)
+ .form-group
+ .col-sm-7
+ input.form-control(type='password', name='password', id='password', placeholder='Password', required)
+ .form-group
+ .col-sm-7
+ input.form-control(type='password', name='confirmPassword', id='confirmPassword', placeholder='Confirm Password', required)
+ .form-group
+ .col-sm-offset-3.col-sm-7
+ button.btn.btn-success(type='submit')
+ i.fa.fa-user-plus
+ | Sign Up \ No newline at end of file
diff --git a/views/stylesheets/authentication.css b/views/stylesheets/authentication.css
new file mode 100644
index 000000000..dea0474e4
--- /dev/null
+++ b/views/stylesheets/authentication.css
@@ -0,0 +1,142 @@
+#email_label {
+ color: blue;
+ margin-top: 10px;
+}
+
+h3,
+label {
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+body {
+ /* background-color: #ccbbcc; */
+ background-color: #251f1f;
+ /* background-image: url(https://bit.ly/2XibZvI);
+ background-repeat: no-repeat;
+ background-size: cover; */
+}
+
+#logo {
+ width: 100px;
+ height: 100px;
+ position: absolute;
+}
+
+.auth_header {
+ text-align: left;
+}
+
+.login,
+.reset {
+ height: 220px;
+}
+
+.forgot {
+ height: 175px;
+}
+
+.signup {
+ height: 273px;
+}
+
+.btn {
+ width: 224px;
+ height: 35px;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ font-style: oblique;
+}
+
+#overlay_signup,
+#overlay_reset,
+#overlay_workspaces {
+ height: 345px;
+}
+
+.workspace-header {
+ margin-left: 20px;
+}
+
+.select-workspace {
+ margin-top: 15px;
+ margin-left: 20px;
+}
+
+#overlay_workspaces {
+ overflow-y: scroll;
+ text-align: left;
+}
+
+.workspaceId {
+ list-style-type: none;
+ font-family: Arial, Helvetica, sans-serif;
+ margin-left: -20px;
+ cursor: grab;
+ padding-bottom: 15px;
+}
+
+.workspaceId:hover {
+ color: red;
+}
+
+#overlay_login {
+ height: 300px;
+}
+
+#overlay_forgot {
+ height: 250px;
+}
+
+#new_user,
+#to_login {
+ right: 15px;
+}
+
+#new_user,
+#to_login,
+#forgot {
+ top: 15px;
+ width: 20px;
+ height: 20px;
+ position: absolute;
+}
+
+#forgot {
+ left: 15px;
+}
+
+.overlay {
+ border: 2px solid yellow;
+ text-align: center;
+ position: absolute;
+ margin: auto;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 400px;
+ background-color: white;
+ border-radius: 8px;
+ box-shadow: 10px 10px 10px #00000099;
+}
+
+.inner {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 230px;
+ margin: auto;
+}
+
+.form-control {
+ width: 200px;
+ margin-bottom: 15px;
+ height: 30px;
+ outline: none;
+ padding-left: 10px;
+ padding-right: 10px;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 16px;
+} \ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index 815e2b477..c08742272 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,86 +1,96 @@
var path = require('path');
var webpack = require('webpack');
const CopyWebpackPlugin = require("copy-webpack-plugin");
+const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
module.exports = {
- mode: 'development',
- entry: {
- bundle: ["./src/client/views/Main.tsx", 'webpack-hot-middleware/client?reload=true'],
- viewer: ["./src/debug/Viewer.tsx", 'webpack-hot-middleware/client?reload=true'],
- test: ["./src/debug/Test.tsx", 'webpack-hot-middleware/client?reload=true'],
- },
- devtool: "source-map",
- node: {
- fs: 'empty',
- module: 'empty',
- dns: 'mock',
- tls: 'mock',
- net: 'mock'
- },
- output: {
- filename: "[name].js",
- path: path.resolve(__dirname, "build"),
- publicPath: "/"
- },
- resolve: {
- extensions: ['.js', '.ts', '.tsx']
- },
- module: {
- rules: [{
- test: [/\.tsx?$/, /\.ts?$/,],
- loader: "awesome-typescript-loader",
- include: path.join(__dirname, 'src')
+ mode: 'development',
+ entry: {
+ bundle: ["./src/client/views/Main.tsx", 'webpack-hot-middleware/client?reload=true'],
+ viewer: ["./src/debug/Viewer.tsx", 'webpack-hot-middleware/client?reload=true'],
+ test: ["./src/debug/Test.tsx", 'webpack-hot-middleware/client?reload=true'],
+ inkControls: ["./src/mobile/InkControls.tsx", 'webpack-hot-middleware/client?reload=true'],
+ imageUpload: ["./src/mobile/ImageUpload.tsx", 'webpack-hot-middleware/client?reload=true'],
},
- {
- test: /\.scss|css$/,
- use: [
- {
- loader: "style-loader"
- },
- {
- loader: "css-loader"
- },
- {
- loader: "sass-loader"
- }
- ]
+ optimization: {
+ noEmitOnErrors: true
},
- {
- test: /\.(jpg|png|pdf)$/,
- use: [
- {
- loader: 'file-loader'
- }
- ]
+ devtool: "source-map",
+ node: {
+ fs: 'empty',
+ module: 'empty',
+ dns: 'mock',
+ tls: 'mock',
+ net: 'mock'
+ },
+ output: {
+ filename: "[name].js",
+ path: path.resolve(__dirname, "build"),
+ publicPath: "/"
+ },
+ resolve: {
+ extensions: ['.js', '.ts', '.tsx']
+ },
+ module: {
+ rules: [
+ {
+ test: [/\.tsx?$/],
+ use: [
+ { loader: 'ts-loader', options: { transpileOnly: true } }
+ ]
+ },
+ {
+ test: /\.scss|css$/,
+ use: [
+ {
+ loader: "style-loader"
+ },
+ {
+ loader: "css-loader"
+ },
+ {
+ loader: "sass-loader"
+ }
+ ]
+ },
+ {
+ test: /\.(jpg|png|pdf)$/,
+ use: [
+ {
+ loader: 'file-loader'
+ }
+ ]
+ },
+ {
+ test: /\.(png|jpg|gif)$/i,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: 8192
+ }
+ }
+ ]
+ }]
},
- {
- test: /\.(png|jpg|gif)$/i,
- use: [
- {
- loader: 'url-loader',
- options: {
- limit: 8192
- }
+ plugins: [
+ new CopyWebpackPlugin([{ from: "deploy", to: path.join(__dirname, "build") }]),
+ new ForkTsCheckerWebpackPlugin({
+ tslint: true, useTypescriptIncrementalApi: true
+ }),
+ new webpack.optimize.OccurrenceOrderPlugin(),
+ new webpack.HotModuleReplacementPlugin(),
+ ],
+ devServer: {
+ compress: false,
+ host: "localhost",
+ contentBase: path.join(__dirname, 'deploy'),
+ port: 4321,
+ hot: true,
+ https: false,
+ overlay: {
+ warnings: true,
+ errors: true
}
- ]
- }]
- },
- plugins: [
- new CopyWebpackPlugin([{ from: "deploy", to: path.join(__dirname, "build") }]),
- new webpack.optimize.OccurrenceOrderPlugin(),
- new webpack.HotModuleReplacementPlugin(),
- new webpack.NoEmitOnErrorsPlugin()
- ],
- devServer: {
- compress: false,
- host: "localhost",
- contentBase: path.join(__dirname, 'deploy'),
- port: 1234,
- hot: true,
- https: false,
- overlay: {
- warnings: true,
- errors: true
}
- }
}; \ No newline at end of file