aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json456
-rw-r--r--package.json1
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/documents/Documents.ts6
-rw-r--r--src/client/views/DocumentButtonBar.tsx2
-rw-r--r--src/client/views/Main.tsx6
-rw-r--r--src/client/views/MainView.tsx10
-rw-r--r--src/client/views/OverlayView.tsx48
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx10
-rw-r--r--src/client/views/collections/CollectionPileView.scss3
-rw-r--r--src/client/views/collections/CollectionPileView.tsx59
-rw-r--r--src/client/views/collections/CollectionSubView.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx92
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx8
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx44
-rw-r--r--src/client/views/nodes/ComparisonBox.scss5
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx131
-rw-r--r--src/client/views/nodes/DocumentView.tsx16
-rw-r--r--src/client/views/nodes/ImageBox.tsx6
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx4
-rw-r--r--src/client/views/nodes/PresBox.tsx10
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx13
-rw-r--r--src/client/views/nodes/WebBox.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx2
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts18
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx16
-rw-r--r--src/client/views/webcam/WebCamLogic.js11
-rw-r--r--src/debug/Repl.tsx3
-rw-r--r--src/debug/Viewer.tsx3
-rw-r--r--src/fields/Doc.ts44
-rw-r--r--src/fields/ScriptField.ts17
-rw-r--r--src/fields/documentSchemas.ts7
-rw-r--r--src/fields/util.ts2
-rw-r--r--src/mobile/ImageUpload.tsx8
-rw-r--r--src/server/ApiManagers/DownloadManager.ts2
-rw-r--r--src/server/DashUploadUtils.ts10
-rw-r--r--src/server/index.ts7
-rw-r--r--src/server/remapUrl.ts3
-rw-r--r--src/server/server_Initialization.ts27
-rw-r--r--src/server/websocket.ts13
-rw-r--r--test/test.ts3
42 files changed, 797 insertions, 349 deletions
diff --git a/package-lock.json b/package-lock.json
index 2e83c295c..8b10d91f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -356,6 +356,19 @@
"integrity": "sha512-bWBbC7VG2jdjbgZMX0qpds8U/3h3anfIqE81L8jmVrgFZw/urEDnBA78ymGGKTTK6ciBXmmJ/xlok+Re41S8ww==",
"dev": true
},
+ "@types/babel-types": {
+ "version": "7.0.7",
+ "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.7.tgz",
+ "integrity": "sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ=="
+ },
+ "@types/babylon": {
+ "version": "6.16.5",
+ "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.5.tgz",
+ "integrity": "sha512-xH2e58elpj1X4ynnKp9qSnWlsRTIs6n3tgLGNfwAGHwePw0mulHQllV34n0T25uYSu1k0hRKkWXF890B1yS47w==",
+ "requires": {
+ "@types/babel-types": "*"
+ }
+ },
"@types/bcrypt-nodejs": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/bcrypt-nodejs/-/bcrypt-nodejs-0.0.30.tgz",
@@ -1388,6 +1401,26 @@
"negotiator": "0.6.2"
}
},
+ "acorn": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
+ "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo="
+ },
+ "acorn-globals": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz",
+ "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=",
+ "requires": {
+ "acorn": "^4.0.4"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
+ }
+ }
+ },
"acorn-jsx": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
@@ -1448,6 +1481,31 @@
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
"integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ=="
},
+ "align-text": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+ "requires": {
+ "kind-of": "^3.0.2",
+ "longest": "^1.0.1",
+ "repeat-string": "^1.5.2"
+ },
+ "dependencies": {
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
"amdefine": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
@@ -1985,6 +2043,29 @@
}
}
},
+ "babel-types": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.4",
+ "to-fast-properties": "^1.0.3"
+ },
+ "dependencies": {
+ "to-fast-properties": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
+ }
+ }
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
+ },
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
@@ -2599,6 +2680,15 @@
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
+ "center-align": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
+ "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
+ "requires": {
+ "align-text": "^0.1.3",
+ "lazy-cache": "^1.0.3"
+ }
+ },
"chai": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
@@ -2622,6 +2712,14 @@
"supports-color": "^5.3.0"
}
},
+ "character-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
+ "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=",
+ "requires": {
+ "is-regex": "^1.0.3"
+ }
+ },
"check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@@ -2702,7 +2800,8 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -2720,11 +2819,13 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2737,15 +2838,18 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -2848,7 +2952,8 @@
},
"inherits": {
"version": "2.0.4",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -2858,6 +2963,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -2870,17 +2976,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.5",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -2897,6 +3006,7 @@
"mkdirp": {
"version": "0.5.3",
"bundled": true,
+ "optional": true,
"requires": {
"minimist": "^1.2.5"
}
@@ -2952,7 +3062,8 @@
},
"npm-normalize-package-bin": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"npm-packlist": {
"version": "1.4.8",
@@ -2977,7 +3088,8 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -2987,6 +3099,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -3055,7 +3168,8 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -3085,6 +3199,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -3102,6 +3217,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -3140,11 +3256,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"yallist": {
"version": "3.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
}
}
}
@@ -3210,6 +3328,21 @@
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
},
+ "clean-css": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
+ "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
+ "requires": {
+ "source-map": "~0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ }
+ }
+ },
"cli-boxes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
@@ -3581,6 +3714,17 @@
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
+ "constantinople": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz",
+ "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==",
+ "requires": {
+ "@types/babel-types": "^7.0.0",
+ "@types/babylon": "^6.16.2",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0"
+ }
+ },
"constants-browserify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
@@ -4635,6 +4779,11 @@
"buffer-indexof": "^1.0.0"
}
},
+ "doctypes": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
+ "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
+ },
"dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
@@ -5079,8 +5228,7 @@
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"etag": {
"version": "1.8.1",
@@ -7173,6 +7321,22 @@
"resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
"integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
},
+ "is-expression": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz",
+ "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=",
+ "requires": {
+ "acorn": "~4.0.2",
+ "object-assign": "^4.0.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
+ }
+ }
+ },
"is-extendable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
@@ -7288,6 +7452,11 @@
"isobject": "^3.0.1"
}
},
+ "is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+ },
"is-redirect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
@@ -7393,6 +7562,11 @@
"resolved": "https://registry.npmjs.org/js-datepicker/-/js-datepicker-4.6.6.tgz",
"integrity": "sha512-kLsE2oHfQM6pzdWNA7Y8HwCHaZIVURItPds7YVJnbe1z1OmQFzN7lfg6yFbDDWOyNjdHZGzWDy3jSDN2W0pNZQ=="
},
+ "js-stringify": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
+ "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds="
+ },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -7552,6 +7726,15 @@
"verror": "1.10.0"
}
},
+ "jstransformer": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
+ "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=",
+ "requires": {
+ "is-promise": "^2.0.0",
+ "promise": "^7.0.1"
+ }
+ },
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
@@ -7611,6 +7794,11 @@
"package-json": "^4.0.0"
}
},
+ "lazy-cache": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
+ },
"lazystream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz",
@@ -7829,6 +8017,11 @@
"object.assign": "^4.1.0"
}
},
+ "longest": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
+ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
+ },
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -9263,7 +9456,7 @@
},
"chownr": {
"version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "resolved": false,
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
},
"ci-info": {
@@ -9569,7 +9762,7 @@
},
"deep-extend": {
"version": "0.6.0",
- "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "resolved": false,
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
},
"defaults": {
@@ -10068,7 +10261,7 @@
},
"glob": {
"version": "7.1.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "resolved": false,
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"fs.realpath": "^1.0.0",
@@ -10156,7 +10349,7 @@
},
"hosted-git-info": {
"version": "2.8.8",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
+ "resolved": false,
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg=="
},
"http-cache-semantics": {
@@ -10292,7 +10485,7 @@
},
"is-ci": {
"version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
+ "resolved": false,
"integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
"requires": {
"ci-info": "^1.5.0"
@@ -10368,7 +10561,7 @@
},
"is-retry-allowed": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
+ "resolved": false,
"integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg=="
},
"is-stream": {
@@ -10877,7 +11070,7 @@
},
"mkdirp": {
"version": "0.5.4",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
+ "resolved": false,
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"requires": {
"minimist": "^1.2.5"
@@ -10885,7 +11078,7 @@
"dependencies": {
"minimist": {
"version": "1.2.5",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "resolved": false,
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
}
}
@@ -10937,7 +11130,7 @@
},
"node-gyp": {
"version": "5.1.0",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz",
+ "resolved": false,
"integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==",
"requires": {
"env-paths": "^2.2.0",
@@ -11051,7 +11244,7 @@
},
"npm-packlist": {
"version": "1.4.8",
- "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
+ "resolved": false,
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
"requires": {
"ignore-walk": "^3.0.1",
@@ -11071,7 +11264,7 @@
},
"npm-profile": {
"version": "4.0.4",
- "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz",
+ "resolved": false,
"integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==",
"requires": {
"aproba": "^1.1.2 || 2",
@@ -11081,7 +11274,7 @@
},
"npm-registry-fetch": {
"version": "4.0.3",
- "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.3.tgz",
+ "resolved": false,
"integrity": "sha512-WGvUx0lkKFhu9MbiGFuT9nG2NpfQ+4dCJwRwwtK2HK5izJEvwDxMeUyqbuMS7N/OkpVCqDorV6rO5E4V9F8lJw==",
"requires": {
"JSONStream": "^1.3.4",
@@ -11516,7 +11709,7 @@
},
"rc": {
"version": "1.2.8",
- "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "resolved": false,
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"requires": {
"deep-extend": "^0.6.0",
@@ -11527,7 +11720,7 @@
"dependencies": {
"minimist": {
"version": "1.2.5",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "resolved": false,
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
}
}
@@ -11586,7 +11779,7 @@
},
"readable-stream": {
"version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "resolved": false,
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
@@ -11607,7 +11800,7 @@
},
"registry-auth-token": {
"version": "3.4.0",
- "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
+ "resolved": false,
"integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
"requires": {
"rc": "^1.1.6",
@@ -11671,7 +11864,7 @@
},
"rimraf": {
"version": "2.7.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "resolved": false,
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"requires": {
"glob": "^7.1.3"
@@ -11970,7 +12163,7 @@
},
"string_decoder": {
"version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "resolved": false,
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
@@ -11978,7 +12171,7 @@
"dependencies": {
"safe-buffer": {
"version": "5.2.0",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+ "resolved": false,
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
}
}
@@ -12290,7 +12483,7 @@
},
"widest-line": {
"version": "2.0.1",
- "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
+ "resolved": false,
"integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
"requires": {
"string-width": "^2.1.1"
@@ -13423,6 +13616,120 @@
"safe-buffer": "^5.1.2"
}
},
+ "pug": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz",
+ "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==",
+ "requires": {
+ "pug-code-gen": "^2.0.2",
+ "pug-filters": "^3.1.1",
+ "pug-lexer": "^4.1.0",
+ "pug-linker": "^3.0.6",
+ "pug-load": "^2.0.12",
+ "pug-parser": "^5.0.1",
+ "pug-runtime": "^2.0.5",
+ "pug-strip-comments": "^1.0.4"
+ }
+ },
+ "pug-attrs": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz",
+ "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==",
+ "requires": {
+ "constantinople": "^3.0.1",
+ "js-stringify": "^1.0.1",
+ "pug-runtime": "^2.0.5"
+ }
+ },
+ "pug-code-gen": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.2.tgz",
+ "integrity": "sha512-kROFWv/AHx/9CRgoGJeRSm+4mLWchbgpRzTEn8XCiwwOy6Vh0gAClS8Vh5TEJ9DBjaP8wCjS3J6HKsEsYdvaCw==",
+ "requires": {
+ "constantinople": "^3.1.2",
+ "doctypes": "^1.1.0",
+ "js-stringify": "^1.0.1",
+ "pug-attrs": "^2.0.4",
+ "pug-error": "^1.3.3",
+ "pug-runtime": "^2.0.5",
+ "void-elements": "^2.0.1",
+ "with": "^5.0.0"
+ }
+ },
+ "pug-error": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz",
+ "integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ=="
+ },
+ "pug-filters": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz",
+ "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==",
+ "requires": {
+ "clean-css": "^4.1.11",
+ "constantinople": "^3.0.1",
+ "jstransformer": "1.0.0",
+ "pug-error": "^1.3.3",
+ "pug-walk": "^1.1.8",
+ "resolve": "^1.1.6",
+ "uglify-js": "^2.6.1"
+ }
+ },
+ "pug-lexer": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz",
+ "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==",
+ "requires": {
+ "character-parser": "^2.1.1",
+ "is-expression": "^3.0.0",
+ "pug-error": "^1.3.3"
+ }
+ },
+ "pug-linker": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz",
+ "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==",
+ "requires": {
+ "pug-error": "^1.3.3",
+ "pug-walk": "^1.1.8"
+ }
+ },
+ "pug-load": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz",
+ "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==",
+ "requires": {
+ "object-assign": "^4.1.0",
+ "pug-walk": "^1.1.8"
+ }
+ },
+ "pug-parser": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz",
+ "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==",
+ "requires": {
+ "pug-error": "^1.3.3",
+ "token-stream": "0.0.1"
+ }
+ },
+ "pug-runtime": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz",
+ "integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw=="
+ },
+ "pug-strip-comments": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz",
+ "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==",
+ "requires": {
+ "pug-error": "^1.3.3"
+ }
+ },
+ "pug-walk": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz",
+ "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA=="
+ },
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -14170,6 +14477,14 @@
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
"dev": true
},
+ "right-align": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+ "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
+ "requires": {
+ "align-text": "^0.1.1"
+ }
+ },
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -15835,6 +16150,11 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
+ "token-stream": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz",
+ "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo="
+ },
"touch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
@@ -16136,6 +16456,50 @@
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
},
+ "uglify-js": {
+ "version": "2.8.29",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
+ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
+ "requires": {
+ "source-map": "~0.5.1",
+ "uglify-to-browserify": "~1.0.0",
+ "yargs": "~3.10.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+ "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+ "requires": {
+ "center-align": "^0.1.1",
+ "right-align": "^0.1.1",
+ "wordwrap": "0.0.2"
+ }
+ },
+ "yargs": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
+ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
+ "requires": {
+ "camelcase": "^1.0.2",
+ "cliui": "^2.1.0",
+ "decamelize": "^1.0.0",
+ "window-size": "0.1.0"
+ }
+ }
+ }
+ },
+ "uglify-to-browserify": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
+ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
+ "optional": true
+ },
"uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -16473,6 +16837,11 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true
},
+ "void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
+ },
"vue-template-compiler": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz",
@@ -17330,6 +17699,20 @@
"infobox-parser": "3.3.1"
}
},
+ "window-size": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
+ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0="
+ },
+ "with": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz",
+ "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=",
+ "requires": {
+ "acorn": "^3.1.0",
+ "acorn-globals": "^3.0.0"
+ }
+ },
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
@@ -17346,6 +17729,11 @@
"its-set": "^1.1.5"
}
},
+ "wordwrap": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+ "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8="
+ },
"wordwrapjs": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz",
diff --git a/package.json b/package.json
index 9952f928c..8bf1e6b74 100644
--- a/package.json
+++ b/package.json
@@ -192,6 +192,7 @@
"prosemirror-state": "^1.3.2",
"prosemirror-transform": "^1.2.3",
"prosemirror-view": "^1.13.8",
+ "pug": "^2.0.4",
"puppeteer": "^3.1.0",
"query-string": "^6.10.1",
"raw-loader": "^1.0.0",
diff --git a/src/Utils.ts b/src/Utils.ts
index bcb215804..ef5002bec 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -43,7 +43,7 @@ export namespace Utils {
}
/**
- * A convenience method. Prepends the full path (i.e. http://localhost:1050) to the
+ * A convenience method. Prepends the full path (i.e. http://localhost:<port>) to the
* requested extension
* @param extension the specified sub-path to append to the window origin
*/
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index b4c11a81b..7f5b62f22 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -110,6 +110,7 @@ export interface DocumentOptions {
_backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor )
color?: string; // foreground color data doc
_color?: string; // foreground color for each template layout doc (overrides color)
+ _clipWidth?: number; // percent transition from before to after in comparisonBox
caption?: RichTextField;
ignoreClick?: boolean;
lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
@@ -125,6 +126,9 @@ export interface DocumentOptions {
curPage?: number;
currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds
displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
+ currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
+ lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
+ activeFrame?: number; // the active frame of a document in a frame base collection
borderRounding?: string;
boxShadow?: string;
dontRegisterChildViews?: boolean;
@@ -559,7 +563,7 @@ export namespace Docs {
}
export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) {
- return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", { targetDropAction: "alias", ...options });
+ return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", { _clipWidth: 50, _backgroundColor: "gray", targetDropAction: "alias", ...options });
}
export function AudioDocument(url: string, options: DocumentOptions = {}) {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 2db5cd3ba..a35a8869c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -121,7 +121,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
dragComplete: dropEv => {
const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
if (this.view0 && linkDoc) {
- Doc.GetProto(linkDoc).linkRelationship = "hyperlink";
+ !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
// we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
// the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 17c001971..6878658a8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,12 +5,16 @@ import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { DocServer } from "../DocServer";
import { AssignAllExtensions } from "../../extensions/General/Extensions";
+import { Networking } from "../Network";
AssignAllExtensions();
+export let resolvedPorts: { server: number, socket: number };
+
(async () => {
const info = await CurrentUserUtils.loadCurrentUser();
- DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
+ resolvedPorts = JSON.parse(await Networking.FetchFromServer("/resolvedPorts"));
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, info.email);
await Docs.Prototypes.initialize();
if (info.id !== "__guest__") {
// a guest will not have an id registered
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index df75cc291..958ba0ce3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -5,7 +5,7 @@ import {
faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -118,7 +118,7 @@ export class MainView extends React.Component {
faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo);
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -139,10 +139,7 @@ export class MainView extends React.Component {
initEventListeners = () => {
window.addEventListener("drop", (e) => { e.preventDefault(); }, false); // drop event handler
- window.addEventListener("dragover", (e) => {
- console.log("MDRAG");
- e.preventDefault();
- }, false); // drag event handler
+ window.addEventListener("dragover", (e) => { e.preventDefault(); }, false); // drag event handler
// click interactions for the context menu
document.addEventListener("pointerdown", this.globalPointerDown);
document.addEventListener("pointerup", this.globalPointerUp);
@@ -246,7 +243,6 @@ export class MainView extends React.Component {
onDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
- console.log("Drop");
}
@action
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index bfa44fe47..cfa869fb2 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,14 +3,17 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
-import { NumCast } from "../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils } from "../../Utils";
+import { NumCast, Cast } from "../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils, setupMoveUpEvents } from "../../Utils";
import { Transform } from "../util/Transform";
import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
import { DocumentView } from "./nodes/DocumentView";
import './OverlayView.scss';
import { Scripting } from "../util/Scripting";
import { ScriptingRepl } from './ScriptingRepl';
+import { DragManager } from "../util/DragManager";
+import { listSpec } from "../../fields/Schema";
+import { List } from "../../fields/List";
export type OverlayDisposer = () => void;
@@ -139,46 +142,51 @@ export class OverlayView extends React.Component {
return remove;
}
+
@computed get overlayDocs() {
const userDocOverlays = Doc.UserDoc().myOverlayDocuments;
if (!userDocOverlays) {
- return (null);
+ return null;
}
return userDocOverlays instanceof Doc && DocListCast(userDocOverlays.data).map(d => {
setTimeout(() => d.inOverlay = true, 0);
let offsetx = 0, offsety = 0;
- const onPointerMove = action((e: PointerEvent) => {
+ const dref = React.createRef<HTMLDivElement>();
+ const onPointerMove = action((e: PointerEvent, down: number[]) => {
if (e.buttons === 1) {
d.x = e.clientX + offsetx;
d.y = e.clientY + offsety;
- e.stopPropagation();
- e.preventDefault();
}
- });
- const onPointerUp = action((e: PointerEvent) => {
- document.removeEventListener("pointermove", onPointerMove);
- document.removeEventListener("pointerup", onPointerUp);
- e.stopPropagation();
- e.preventDefault();
+ if (e.metaKey) {
+ const dragData = new DragManager.DocumentDragData([d]);
+ d.removeDropProperties = new List<string>(["inOverlay"]);
+ dragData.offset = [-offsetx, -offsety];
+ dragData.dropAction = "move";
+ dragData.removeDocument = (doc: Doc | Doc[]) => {
+ const docs = (doc instanceof Doc) ? [doc] : doc;
+ docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null), "data", d));
+ return true;
+ };
+ dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+ return dragData.removeDocument!(doc) ? addDocument(doc) : false;
+ };
+ DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
+ return true;
+ }
+ return false;
});
const onPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction);
offsetx = NumCast(d.x) - e.clientX;
offsety = NumCast(d.y) - e.clientY;
- e.stopPropagation();
- e.preventDefault();
- document.addEventListener("pointermove", onPointerMove);
- document.addEventListener("pointerup", onPointerUp);
};
- return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)` }}>
+ return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}>
<DocumentView
Document={d}
LibraryPath={emptyPath}
ChromeHeight={returnZero}
rootSelected={returnTrue}
- // isSelected={returnFalse}
- // select={emptyFunction}
- // layoutKey={"layout"}
bringToFront={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 1e3bf11de..cc7a9f5ac 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -240,13 +240,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
toggle: this.toggleVisibility,
color: this.color
};
+ const showChrome = (chromeStatus !== 'view-mode' && chromeStatus !== 'disabled');
+ const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
return this.collapsed ? (null) :
<div style={{ position: "relative" }}>
- {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
+ {showChrome ?
<div className="collectionStackingView-addDocumentButton"
style={{
- width: style.columnWidth / style.numGroupColumns,
- padding: NumCast(this.props.parent.layoutDoc._yPadding)
+ //width: style.columnWidth / style.numGroupColumns,
+ padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`
}}>
<EditableView {...newEditableViewProps} />
</div> : null
@@ -254,7 +256,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
<div className={`collectionStackingView-masonryGrid`}
ref={this._contRef}
style={{
- padding: `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px`,
+ padding: stackPad,
width: this.props.parent.NodeWidth,
gridGap: this.props.parent.gridGap,
gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ""),
diff --git a/src/client/views/collections/CollectionPileView.scss b/src/client/views/collections/CollectionPileView.scss
index ac874b663..48d07e42b 100644
--- a/src/client/views/collections/CollectionPileView.scss
+++ b/src/client/views/collections/CollectionPileView.scss
@@ -5,4 +5,7 @@
height: 100%;
width: 100%;
overflow: visible;
+ .collectionPileView-innards {
+ width:100%;
+ }
}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index e3bcf2a21..020a87b2e 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,18 +1,17 @@
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { HeightSym, Opt, WidthSym, Doc } from "../../../fields/Doc";
import { ScriptField } from "../../../fields/ScriptField";
import { BoolCast, NumCast, StrCast } from "../../../fields/Types";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionPileView.scss";
import React = require("react");
import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
import { SelectionManager } from "../../util/SelectionManager";
-import { UndoManager } from "../../util/UndoManager";
+import { UndoManager, undoBatch } from "../../util/UndoManager";
import { SnappingManager } from "../../util/SnappingManager";
+import { DragManager } from "../../util/DragManager";
@observer
export class CollectionPileView extends CollectionSubView(doc => doc) {
@@ -20,10 +19,12 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
_doubleTap: boolean | undefined = false;
_originalChrome: string = "";
@observable _contentsActive = true;
- @observable _layoutEngine = "pass";
@observable _collapsed: boolean = false;
@observable _childClickedScript: Opt<ScriptField>;
componentDidMount() {
+ if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") {
+ this.Document._layoutEngine = "pass";
+ }
this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
this.layoutDoc._chromeStatus = "disabled";
this.layoutDoc.hideFilterView = true;
@@ -33,49 +34,54 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
this.layoutDoc._chromeStatus = this._originalChrome;
}
- layoutEngine = () => this._layoutEngine;
+ layoutEngine = () => StrCast(this.Document._layoutEngine);
@computed get contents() {
- return <div className="collectionPileView-innards" style={{
- width: "100%",
- pointerEvents: this.layoutEngine() !== "pass" && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none"
- }} >
+ return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" ? undefined : "none" }} >
<CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
</div>;
}
-
- specificMenu = (e: React.MouseEvent) => {
- const layoutItems: ContextMenuProps[] = [];
- const doc = this.props.Document;
-
- ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
- }
-
toggleStarburst = action(() => {
- if (this._layoutEngine === 'starburst') {
+ if (this.layoutEngine() === 'starburst') {
const defaultSize = 110;
this.layoutDoc._overflow = undefined;
+ this.childDocs.forEach(d => Doc.iconify(d));
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
- this._layoutEngine = 'pass';
+ Doc.pileup(this.childDocs);
+ this.layoutDoc._panX = 0;
+ this.layoutDoc._panY = -10;
+ this.props.Document._layoutEngine = 'pass';
} else {
const defaultSize = 25;
this.layoutDoc._overflow = 'visible';
!this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
!this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
- if (this._layoutEngine === 'pass') {
+ if (this.layoutEngine() === 'pass') {
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
}
+ this.layoutDoc._panX = this.layoutDoc._panY = 0;
this.layoutDoc._width = this.layoutDoc._height = defaultSize;
- this._layoutEngine = 'starburst';
+ this.props.Document._layoutEngine = 'starburst';
}
});
+ @undoBatch
+ @action
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ if (super.onInternalDrop(e, de)) {
+ if (de.complete.docDragData) {
+ Doc.pileup(this.childDocs);
+ }
+ }
+ return true;
+ }
+
_undoBatch: UndoManager.Batch | undefined;
pointerDown = (e: React.PointerEvent) => {
let dist = 0;
@@ -107,20 +113,17 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
}
onClick = (e: React.MouseEvent) => {
- if (e.button === 0 && (this._doubleTap || this.layoutEngine() === "starburst")) {
+ if (e.button === 0 && this._doubleTap) {
SelectionManager.DeselectAll();
this.toggleStarburst();
e.stopPropagation();
}
- // else if (this.layoutEngine() === "pass") {
- // runInAction(() => this._contentsActive = false);
- // setTimeout(action(() => this._contentsActive = true), 300);
- // }
}
render() {
- return <div className={"collectionPileView"} onContextMenu={this.specificMenu} onClick={this.onClick} onPointerDown={this.pointerDown}
+ return <div className={"collectionPileView"} onClick={this.onClick} onPointerDown={this.pointerDown}
+ ref={this.createDashEventsTarget}
style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
{this.contents}
</div>;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 53acc15c3..423eb1d90 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -218,7 +218,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
if (docDragData) {
let added = false;
- if (docDragData.dropAction || docDragData.userDropAction) {
+ const dropaction = docDragData.dropAction || docDragData.userDropAction;
+ if (dropaction && dropaction !== "move") {
added = this.addDocument(docDragData.droppedDocuments);
} else if (docDragData.moveDocument) {
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
@@ -451,7 +452,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (generatedDocuments.length) {
const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d));
if (set) {
- addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!));
+ addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!)!);
} else {
generatedDocuments.forEach(addDocument);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 7a84fcde1..5478a1c4a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -51,12 +51,21 @@
}
.backKeyframe {
right:45;
+ svg {
+ display:block;
+ margin:auto;
+ }
}
.numKeyframe {
right:25;
+ text-align:center;
}
.fwdKeyframe {
right:5;
+ svg {
+ display:block;
+ margin:auto;
+ }
}
.collectionfreeformview-placeholder {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0b61aad8a..0639d74a8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -18,7 +18,7 @@ import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
+import { Docs, DocUtils } from "../../../documents/Documents";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager, dropActionType } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
@@ -56,6 +56,7 @@ export const panZoomSchema = createSchema({
scale: "number",
currentTimecode: "number",
displayTimecode: "number",
+ currentFrame: "number",
arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
@@ -126,44 +127,62 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
FormattedTextBox.SelectOnLoad = 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);
}
- addDocument = (newBox: Doc | Doc[]) => {
- if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
- CollectionFreeFormDocumentView.setupKeyframes((newBox instanceof Doc) ? [newBox] : newBox, this.Document.currentTimecode, this.props.Document);
- }
-
+ addDocument = action((newBox: Doc | Doc[]) => {
+ let retVal = false;
if (newBox instanceof Doc) {
- const added = this.props.addDocument(newBox);
- added && this.bringToFront(newBox);
- added && this.updateCluster(newBox);
- return added;
+ retVal = this.props.addDocument(newBox);
+ retVal && this.bringToFront(newBox);
+ retVal && this.updateCluster(newBox);
} else {
- return this.props.addDocument(newBox);
+ retVal = this.props.addDocument(newBox);
// bcz: deal with clusters
}
- }
+ if (retVal) {
+ const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
+ for (let i = 0; i < newBoxes.length; i++) {
+ const newBox = newBoxes[i];
+ if (newBox.activeFrame !== undefined) {
+ const x = newBox.x;
+ const y = newBox.y;
+ delete newBox["x-indexed"];
+ delete newBox["y-indexed"];
+ delete newBox["opacity-indexed"];
+ delete newBox.x;
+ delete newBox.y;
+ delete newBox.activeFrame;
+ newBox.x = x;
+ newBox.y = y;
+ }
+ }
+ if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentFrame);
+ }
+ }
+ return retVal;
+ })
@undoBatch
@action
nextKeyframe = (): void => {
- const currentTimecode = this.Document.currentTimecode;
- if (currentTimecode === undefined) {
- this.Document.currentTimecode = 0;
- CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
+ const currentFrame = this.Document.currentFrame;
+ if (currentFrame === undefined) {
+ this.Document.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
- CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentTimecode || 0);
- this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) + 1);
- this.Document.lastTimecode = Math.max(NumCast(this.Document.currentTimecode), NumCast(this.Document.lastTimecode));
+ CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+ this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
}
@undoBatch
@action
prevKeyframe = (): void => {
- const currentTimecode = this.Document.currentTimecode;
- if (currentTimecode === undefined) {
- this.Document.currentTimecode = 0;
- CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0, this.props.Document);
+ const currentFrame = this.Document.currentFrame;
+ if (currentFrame === undefined) {
+ this.Document.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
- this.Document.currentTimecode = Math.max(0, (currentTimecode || 0) - 1);
+ this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
}
private selectDocuments = (docs: Doc[]) => {
@@ -191,6 +210,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const [xp, yp] = xf.transformPoint(de.x, de.y);
const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ if (!this.isAnnotationOverlay && de.complete.linkDragData && de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
+ const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
+ this.props.addDocument(source);
+ (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceDocument },
+ "doc annotation")); // TODODO this is where in text links get passed
+ e.stopPropagation();
+ return true;
+ }
if (super.onInternalDrop(e, de)) {
if (de.complete.docDragData) {
if (de.complete.docDragData.droppedDocuments.length) {
@@ -206,8 +233,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
for (let i = 0; i < droppedDocs.length; i++) {
const d = droppedDocs[i];
const layoutDoc = Doc.Layout(d);
- if (this.Document.currentTimecode !== undefined && !this.props.isAnnotationOverlay) {
- CollectionFreeFormDocumentView.setValues(this.Document.currentTimecode, d, x + NumCast(d.x) - dropX, y + NumCast(d.y) - dropY, Cast(d.opacity, "number", null));
+ if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
+ CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropX, y + vals.y - dropY, vals.opacity);
} else {
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
@@ -979,12 +1007,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
}
const layoutDoc = Doc.Layout(params.pair.layout);
- const { x, y, opacity } = this.Document.currentTimecode === undefined ? params.pair.layout :
- CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentTimecode || 0);
+ const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
+ CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
const { z, color, zIndex } = params.pair.layout;
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
- transition: StrCast(layoutDoc.transition), opacity: Cast(opacity, "number", null),
+ transition: StrCast(layoutDoc.transition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
};
}
@@ -1363,13 +1391,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
{!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
this.placeholder : this.marqueeView}
<CollectionFreeFormOverlayView elements={this.elementFunc} />
- {this.isAnnotationOverlay || !this.props.isSelected() ? (null) :
+ {this.isAnnotationOverlay || !this.props.isSelected() || this.props.Document._viewType === CollectionViewType.Pile ? (null) :
<>
<div key="back" className="backKeyframe" onClick={this.prevKeyframe}>
<FontAwesomeIcon icon={"caret-left"} size={"lg"} />
</div>
- <div key="num" className="numKeyframe" >
- {NumCast(this.props.Document.currentTimecode)}
+ <div key="num" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }} onClick={action(() => this.Document.editing = !this.Document.editing)} >
+ {NumCast(this.Document.currentFrame)}
</div>
<div key="fwd" className="fwdKeyframe" onClick={this.nextKeyframe}>
<FontAwesomeIcon icon={"caret-right"} size={"lg"} />
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0244dfc56..ed70ac9e8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -334,9 +334,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
_LODdisable: true,
title: "a nested collection",
});
- // const dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
- // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
- // this.marqueeInkDelete(inkData);
+ selected.forEach(d => d.context = newCollection);
this.hideMarquee();
return newCollection;
}
@@ -347,8 +345,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
SelectionManager.DeselectAll();
selected.forEach(d => this.props.removeDocument(d));
const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
- this.props.addDocument(newCollection);
- this.props.selectDocuments([newCollection], []);
+ this.props.addDocument(newCollection!);
+ this.props.selectDocuments([newCollection!], []);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 57f484214..682aed8f5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -69,19 +69,24 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
public static getValues(doc: Doc, time: number) {
+ const timecode = Math.round(time);
return ({
- x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= time && x !== undefined) || p === undefined ? x : p, undefined as any as number),
- y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= time && y !== undefined) || p === undefined ? y : p, undefined as any as number),
- opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= time || p === undefined ? o : p, undefined as any as number),
+ x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number),
+ y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number),
+ opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number),
});
}
- public static setValues(timecode: number, d: Doc, x?: number, y?: number, opacity?: number) {
+ public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) {
+ const timecode = Math.round(time);
+ Cast(d["x-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = x as any as number;
+ Cast(d["y-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = y as any as number;
Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number;
- Cast(d["y-indexed"], listSpec("number"), null)[timecode] = y as any as number;
+ Cast(d["y-indexed"], listSpec("number"), [])[timecode] = y as any as number;
Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number;
}
- public static updateKeyframe(docs: Doc[], timecode: number) {
+ public static updateKeyframe(docs: Doc[], time: number) {
+ const timecode = Math.round(time);
docs.forEach(doc => {
const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
@@ -99,18 +104,21 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010);
}
- public static setupKeyframes(docs: Doc[], timecode: number, collection: Doc) {
- docs.forEach(doc => {
- doc["x-indexed"] = new List<number>(numberRange(timecode).map(i => undefined) as any as number[]);
- doc["y-indexed"] = new List<number>(numberRange(timecode).map(i => undefined) as any as number[]);
- doc["opacity-indexed"] = new List<number>(numberRange(timecode).map(i => 0));
- (doc["x-indexed"] as any).push(NumCast(doc.x));
- (doc["y-indexed"] as any).push(NumCast(doc.y));
- (doc["opacity-indexed"] as any).push(NumCast(doc.opacity, 1));
- doc.displayTimecode = ComputedField.MakeFunction("collection ? collection.currentTimecode : 0", {}, { collection });
- doc.x = ComputedField.MakeInterpolated("x", "displayTimecode");
- doc.y = ComputedField.MakeInterpolated("y", "displayTimecode");
- doc.opacity = ComputedField.MakeInterpolated("opacity", "displayTimecode");
+ public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) {
+ docs.forEach((doc, i) => {
+ const curTimecode = progressivize ? i : timecode;
+ const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
+ const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
+ const olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < i ? 0 : 1));
+ xlist[Math.max(curTimecode - 1, 0)] = xlist[curTimecode] = NumCast(doc.x);
+ ylist[Math.max(curTimecode - 1, 0)] = ylist[curTimecode] = NumCast(doc.y);
+ doc["x-indexed"] = xlist;
+ doc["y-indexed"] = ylist;
+ doc["opacity-indexed"] = olist;
+ doc.activeFrame = ComputedField.MakeFunction("self.context ? (self.context.currentFrame||0) : 0");
+ doc.x = ComputedField.MakeInterpolated("x", "activeFrame");
+ doc.y = ComputedField.MakeInterpolated("y", "activeFrame");
+ doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame");
});
}
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
index 3d48d96e2..7849c9976 100644
--- a/src/client/views/nodes/ComparisonBox.scss
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -2,7 +2,6 @@
border-radius: inherit;
width: 100%;
height: 100%;
- background-color: grey;
position: absolute;
z-index: 0;
pointer-events: none;
@@ -17,7 +16,6 @@
.beforeBox-cont {
height: 100%;
overflow: hidden;
- background-color: rgb(240, 240, 240);
}
}
@@ -26,7 +24,7 @@
height: 100%;
width: 3px;
display: inline-block;
- background: gray;
+ background: white;
cursor: ew-resize;
.slide-handle {
position: absolute;
@@ -48,7 +46,6 @@
height: 100%;
width: 100%;
overflow: hidden;
- background-color: lightgray;
}
.clear-button {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 7a4d40db1..77e07ec0c 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -1,13 +1,10 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEye } from '@fortawesome/free-regular-svg-icons';
-import { faAsterisk, faBrain, faFileAudio, faImage, faPaintBrush, faTimes, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, runInAction, Lambda } from 'mobx';
+import { action, computed, observable, runInAction, Lambda, IReactionDisposer } from 'mobx';
import { observer } from "mobx-react";
-import { Doc } from '../../../fields/Doc';
+import { Doc, Opt } from '../../../fields/Doc';
import { documentSchema } from '../../../fields/documentSchemas';
import { createSchema, makeInterface } from '../../../fields/Schema';
-import { NumCast, Cast } from '../../../fields/Types';
+import { NumCast, Cast, StrCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
@@ -17,9 +14,7 @@ import { ContentFittingDocumentView } from './ContentFittingDocumentView';
import { undoBatch } from '../../util/UndoManager';
import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
import { SnappingManager } from '../../util/SnappingManager';
-
-library.add(faImage, faEye as any, faPaintBrush, faBrain);
-library.add(faFileAudio, faAsterisk);
+import { DocumentViewProps } from './DocumentView';
export const comparisonSchema = createSchema({});
@@ -28,17 +23,16 @@ const ComparisonDocument = makeInterface(comparisonSchema, documentSchema);
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) {
- protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
-
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); }
+ protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+ private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
- private _beforeDropDisposer?: DragManager.DragDropDisposer;
- private _afterDropDisposer?: DragManager.DragDropDisposer;
- private resizeUpdater: Lambda | undefined;
+ @observable _animating = "";
- protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string) => {
+ protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
+ this._disposers[disposerId]?.();
if (ele) {
- return DragManager.MakeDropTarget(ele, (event, dropEvent) => this.dropHandler(event, dropEvent, fieldKey), this.layoutDoc);
+ this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.dropHandler(e, dropEvent, fieldKey), this.layoutDoc);
}
}
@@ -48,36 +42,22 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
if (droppedDocs?.length) {
this.dataDoc[fieldKey] = droppedDocs[0];
- droppedDocs[0].isBackgound = true;
}
}
- componentWillMount() {
- this.dataDoc.clipWidth = this.props.PanelWidth() / 2;
-
- //preserve before/after ratio during resizing
- this.resizeUpdater = computed(() => this.props.PanelWidth()).observe(({ oldValue, newValue }) =>
- this.dataDoc.clipWidth = NumCast(this.dataDoc.clipWidth) / (oldValue || 0) * newValue
- );
- }
-
- componentWillUnmount() {
- this.resizeUpdater?.();
- }
-
private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, action(() => {
- this._animating = true;
- this.dataDoc.clipWidth = targetWidth;
- setTimeout(action(() => this._animating = false), 1000);
+ this._animating = "all 1s";
+ this.layoutDoc._clipWidth = targetWidth * 100 / this.props.PanelWidth();
+ setTimeout(action(() => this._animating = ""), 1000);
}), false);
}
@action
private onPointerMove = ({ movementX }: PointerEvent) => {
- const width = movementX * this.props.ScreenToLocalTransform().Scale + NumCast(this.dataDoc.clipWidth);
+ const width = movementX * this.props.ScreenToLocalTransform().Scale + NumCast(this.layoutDoc._clipWidth) / 100 * this.props.PanelWidth();
if (width && width > 5 && width < this.props.PanelWidth()) {
- this.dataDoc.clipWidth = width;
+ this.layoutDoc._clipWidth = width * 100 / this.props.PanelWidth();
}
return false;
}
@@ -85,66 +65,43 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C
@undoBatch
clearDoc = (e: React.MouseEvent, fieldKey: string) => {
e.stopPropagation;
- e.preventDefault;
delete this.dataDoc[fieldKey];
}
- @observable _animating = false;
render() {
- const beforeDoc = Cast(this.dataDoc.beforeDoc, Doc, null);
- const afterDoc = Cast(this.dataDoc.afterDoc, Doc, null);
- const clipWidth = NumCast(this.dataDoc.clipWidth);
+ const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
+ const childProps: DocumentViewProps = { ...this.props, pointerEvents: false, parentActive: this.props.active };
+ const clearButton = (which: string) => {
+ return <div className={`clear-button ${which}`} onPointerDown={e => e.stopPropagation()} onClick={e => this.clearDoc(e, `${which}Doc`)}>
+ <FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
+ </div>;
+ };
+ const displayDoc = (which: string) => {
+ const whichDoc = Cast(this.dataDoc[`${which}Doc`], Doc, null);
+ return whichDoc ? <>
+ <ContentFittingDocumentView {...childProps} Document={whichDoc} />
+ {clearButton(which)}
+ </> : // placeholder image if doc is missing
+ <div className="placeholder">
+ <FontAwesomeIcon className="upload-icon" icon={"cloud-upload-alt"} size="lg" />
+ </div>;
+ };
+ const displayBox = (which: string, index: number, cover: number) => {
+ return <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }}
+ onPointerDown={e => this.registerSliding(e, cover)}
+ ref={ele => this.createDropTarget(ele, `${which}Doc`, index)} >
+ {displayDoc(which)}
+ </div>;
+ };
+
return (
<div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}`}>
- <div className="afterBox-cont" key={"after"} onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() - 5)}
- ref={(ele) => {
- this._afterDropDisposer?.();
- this._afterDropDisposer = this.createDropTarget(ele, "afterDoc");
- }}>
- {afterDoc ? <>
- <ContentFittingDocumentView {...this.props}
- Document={afterDoc}
- pointerEvents={false}
- parentActive={this.props.active}
- />
- <div className="clear-button after" onClick={e => this.clearDoc(e, "afterDoc")}>
- <FontAwesomeIcon className="clear-button after" icon={faTimes} size="sm" />
- </div>
- </> :
- <div className="placeholder">
- <FontAwesomeIcon className="upload-icon" icon={faCloudUploadAlt} size="lg" />
- </div>}
- </div>
- <div className="clip-div" onPointerDown={e => this.registerSliding(e, 5)} style={{ width: clipWidth + "px", transition: this._animating ? "all 1s" : undefined }}>
- {/* wraps around before image and slider bar */}
- <div
- className="beforeBox-cont"
- key={"before"}
- ref={(ele) => {
- this._beforeDropDisposer?.();
- this._beforeDropDisposer = this.createDropTarget(ele, "beforeDoc");
- }}
- style={{ width: this.props.PanelWidth() }}>
- {
- beforeDoc ?
- <>
- <ContentFittingDocumentView {...this.props}
- Document={beforeDoc}
- pointerEvents={false}
- parentActive={this.props.active} />
- <div className="clear-button before" onClick={e => this.clearDoc(e, "beforeDoc")}>
- <FontAwesomeIcon className="clear-button before" icon={faTimes} size="sm" />
- </div>
- </>
- :
- <div className="placeholder">
- <FontAwesomeIcon className="upload-icon" icon={faCloudUploadAlt} size="lg" />
- </div>
- }
- </div>
+ {displayBox("after", 1, this.props.PanelWidth() - 5)}
+ <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
+ {displayBox("before", 0, 5)}
</div>
- <div className="slide-bar" style={{ left: `calc(${NumCast(this.dataDoc.clipWidth) * 100 / this.props.PanelWidth()}% - 0.5px)` }}>
+ <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)` }}>
<div className="slide-handle" />
</div>
</div >);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 993cabc36..00d19752f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1111,6 +1111,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}), 400);
});
+ renderLock() {
+ return (this.Document.isBackground !== undefined || this.isSelected(false)) &&
+ ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) &&
+ this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
+ <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
+ <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
+ </div>
+ : (null);
+ }
+
render() {
if (!(this.props.Document instanceof Doc)) return (null);
const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
@@ -1161,11 +1171,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
<div className="documentView-contentBlocker" />
</> :
this.innards}
- {(this.Document.isBackground !== undefined || this.isSelected(false)) && (this.Document.type === DocumentType.COL || this.Document.type === DocumentType.IMG) && this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
- <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
- <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
- </div>
- : (null)}
+ {this.renderLock()}
</div>;
{ this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 77abfef1d..6913dfbc7 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -266,7 +266,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) {
requestImageSize(imgPath).then(action((inquiredSize: any) => {
const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]) % 180;
- const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize;
+ const rotatedNativeSize = { width: inquiredSize.width, height: inquiredSize.height };
+ if (inquiredSize.orientation === 6 || rotation === 90 || rotation === 270) {
+ rotatedNativeSize.width = inquiredSize.height;
+ rotatedNativeSize.height = inquiredSize.width;
+ }
const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width;
if (this.layoutDoc[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) {
this.layoutDoc._height = this.layoutDoc[WidthSym]() * rotatedAspect;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 956d6556b..3cbe3e494 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -99,9 +99,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
<div className="keyValuePair-td-key-container">
<button style={hover} className="keyValuePair-td-key-delete" onClick={undoBatch(() => {
if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
- props.Document[props.fieldKey] = undefined;
+ delete props.Document[props.fieldKey];
}
- else props.Document.proto![props.fieldKey] = undefined;
+ else delete props.Document.proto![props.fieldKey];
})}>
X
</button>
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index aeb77a894..05306c29f 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -59,10 +59,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
next = () => {
this.updateCurrentPresentation();
const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
- const lastFrame = Cast(presTargetDoc.lastTimecode, "number", null);
- const curFrame = NumCast(presTargetDoc.currentTimecode);
+ const lastFrame = Cast(presTargetDoc.lastFrame, "number", null);
+ const curFrame = NumCast(presTargetDoc.currentFrame);
if (lastFrame !== undefined && curFrame < lastFrame) {
- presTargetDoc.currentTimecode = curFrame + 1;
+ presTargetDoc.currentFrame = curFrame + 1;
}
else if (this.childDocs[this.itemIndex + 1] !== undefined) {
let nextSelected = this.itemIndex + 1;
@@ -199,8 +199,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
if (index >= 0 && index < this.childDocs.length) {
this.rootDoc._itemIndex = index;
const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
- if (presTargetDoc.lastTimecode !== undefined) {
- presTargetDoc.currentTimecode = 0;
+ if (presTargetDoc.lastFrame !== undefined) {
+ presTargetDoc.currentFrame = 0;
}
if (!this.layoutDoc.presStatus) {
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 5d4af2d77..29e3c008a 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -6,7 +6,7 @@ import { action, computed, IReactionDisposer, observable, runInAction } from "mo
import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
import { Cast, NumCast } from "../../../fields/Types";
import { VideoField } from "../../../fields/URLField";
import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils";
@@ -18,6 +18,8 @@ import { ViewBoxBaseComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./ScreenshotBox.scss";
+import { Doc, WidthSym, HeightSym } from "../../../fields/Doc";
+import { OverlayView } from "../OverlayView";
const path = require('path');
type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
@@ -72,7 +74,14 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y),
_width: 150, _height: height / width * 150, title: "--screenshot--"
});
- this.props.addDocument?.(imageSummary);
+ if (!this.props.addDocument || this.props.addDocument === returnFalse) {
+ const spt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ imageSummary.x = spt[0];
+ imageSummary.y = spt[1];
+ Cast(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null)?.data, listSpec(Doc), []).push(imageSummary);
+ } else {
+ this.props.addDocument?.(imageSummary);
+ }
}
}, 500);
});
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f80cea941..a91d4dfd9 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -106,7 +106,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
} // else it's an HTMLfield
} else if (field?.url) {
const result = await WebRequest.get(Utils.CorsProxy(field.url.href));
- this.dataDoc.text = htmlToText.fromString(result.content);
+ if (result) {
+ this.dataDoc.text = htmlToText.fromString(result.content);
+ }
}
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index b8fbe3420..fc131cd38 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -869,7 +869,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type.name === "link");
- const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
+ const link = view.state.schema.mark(view.state.schema.marks.link, { href: Utils.prepend(`/doc/${linkId}`), location: "onRight", title: title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 2f7d23021..0e3e7f91e 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -154,15 +154,12 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const originalDoc = layoutDoc.rootDocument || layoutDoc;
if (originalDoc instanceof Doc) {
const layoutKey = StrCast(originalDoc.layoutKey);
- const newDoc = Docs.Create.TextDocument("", {
- layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
- layoutKey,
- _singleLine: BoolCast(originalDoc._singleLine),
- x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
- });
+ const newDoc = Doc.MakeCopy(originalDoc, true);
+ newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
}
+ Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
}
@@ -178,15 +175,12 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const originalDoc = layoutDoc.rootDocument || layoutDoc;
if (force || props.Document._singleLine) {
const layoutKey = StrCast(originalDoc.layoutKey);
- const newDoc = Docs.Create.TextDocument("", {
- layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
- layoutKey,
- _singleLine: BoolCast(originalDoc._singleLine),
- x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
- });
+ const newDoc = Doc.MakeCopy(originalDoc, true);
+ newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
}
+ Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
return true;
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index a2a6882b9..475fef5b2 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -100,12 +100,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
e.stopPropagation();
this.rootDoc.presProgressivize = !this.rootDoc.presProgressivize;
const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null);
- if (this.rootDoc.presProgressivize && !rootTarget?.lastTimecode) {
- const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
- rootTarget.currentTimecode = 0;
- CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, this.presBox);
- docs.forEach((d, i) => numberRange(docs.length - i).forEach(f => Cast(d["opacity-indexed"], listSpec("number"), [])[f + i] = 1));
- rootTarget.lastTimecode = docs.length - 1;
+ const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
+ if (this.rootDoc.presProgressivize) {
+ rootTarget.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
+ rootTarget.lastFrame = docs.length - 1;
}
}
@@ -216,7 +215,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
<strong className="presElementBox-name">
{`${this.indexInPres + 1}. ${this.targetDoc?.title}`}
</strong>
- <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument?.(this.rootDoc)}>X</button>
+ <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => {
+ this.props.removeDocument?.(this.rootDoc);
+ e.stopPropagation();
+ }}>X</button>
<br />
</>}
<div className="presElementBox-buttons">
diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js
index c847b8656..a8a2f5fa4 100644
--- a/src/client/views/webcam/WebCamLogic.js
+++ b/src/client/views/webcam/WebCamLogic.js
@@ -1,5 +1,8 @@
'use strict';
import io from "socket.io-client";
+import {
+ resolvedPorts
+} from "../Main";
var socket;
var isChannelReady = false;
@@ -29,7 +32,7 @@ export function initialize(roomName, handlerUI) {
room = roomName;
- socket = io.connect(`${window.location.protocol}//${window.location.hostname}:${4321}`);
+ socket = io.connect(`${window.location.protocol}//${window.location.hostname}:${resolvedPorts.socket}`);
if (room !== '') {
socket.emit('create or join', room);
@@ -104,9 +107,9 @@ export function initialize(roomName, handlerUI) {
navigator.mediaDevices.getUserMedia({
- audio: true,
- video: true
- })
+ audio: true,
+ video: true
+ })
.then(gotStream)
.catch(function (e) {
alert('getUserMedia() error: ' + e.name);
diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx
index d541c8009..be53c0b9b 100644
--- a/src/debug/Repl.tsx
+++ b/src/debug/Repl.tsx
@@ -7,6 +7,7 @@ import { makeInterface } from '../fields/Schema';
import { ObjectField } from '../fields/ObjectField';
import { RefField } from '../fields/RefField';
import { DocServer } from '../client/DocServer';
+import { resolvedPorts } from '../client/views/Main';
@observer
class Repl extends React.Component {
@@ -61,6 +62,6 @@ class Repl extends React.Component {
}
(async function () {
- DocServer.init(window.location.protocol, window.location.hostname, 4321, "repl");
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "repl");
ReactDOM.render(<Repl />, document.getElementById("root"));
})(); \ No newline at end of file
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index ddddee3be..0ca067ed3 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -14,6 +14,7 @@ import { RichTextField } from '../fields/RichTextField';
import { DateField } from '../fields/DateField';
import { ScriptField } from '../fields/ScriptField';
import CursorField from '../fields/CursorField';
+import { resolvedPorts } from '../client/views/Main';
DateField;
URLField;
@@ -182,7 +183,7 @@ class Viewer extends React.Component {
}
(async function () {
- await DocServer.init(window.location.protocol, window.location.hostname, 4321, "viewer");
+ await DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "viewer");
ReactDOM.render((
<div style={{ position: "absolute", width: "100%", height: "100%" }}>
<Viewer />
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 9f38f4369..1ea686cbb 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1028,28 +1028,32 @@ export namespace Doc {
if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
}
- export function pileup(selected: Doc[], x: number, y: number) {
- const newCollection = Docs.Create.PileDocument(selected, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
+ export function pileup(docList: Doc[], x?: number, y?: number) {
let w = 0, h = 0;
- selected.forEach((d, i) => {
- Doc.iconify(d);
- w = Math.max(d[WidthSym](), w);
- h = Math.max(d[HeightSym](), h);
- });
- h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable
- selected.forEach((d, i) => {
- d.x = Math.cos(Math.PI * 2 * i / selected.length) * 10 - w / 2;
- d.y = Math.sin(Math.PI * 2 * i / selected.length) * 10 - h / 2;
- d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ runInAction(() => {
+ docList.forEach(d => {
+ Doc.iconify(d);
+ w = Math.max(d[WidthSym](), w);
+ h = Math.max(d[HeightSym](), h);
+ });
+ h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable
+ docList.forEach((d, i) => {
+ d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
+ d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
+ d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ });
});
- newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
- newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
- newCollection._width = newCollection._height = 110;
- //newCollection.borderRounding = "40px";
- newCollection._jitterRotation = 10;
- newCollection._backgroundColor = "gray";
- newCollection._overflow = "visible";
- return newCollection;
+ if (x !== undefined && y !== undefined) {
+ const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: x - 55, y: y - 55, _width: 110, _height: 100, _LODdisable: true });
+ newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
+ newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
+ newCollection._width = newCollection._height = 110;
+ //newCollection.borderRounding = "40px";
+ newCollection._jitterRotation = 10;
+ newCollection._backgroundColor = "gray";
+ newCollection._overflow = "visible";
+ return newCollection;
+ }
}
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 503c60790..5192af407 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -65,14 +65,14 @@ export class ScriptField extends ObjectField {
@serializable(autoObject())
private captures?: ProxyField<Doc>;
- constructor(script: CompiledScript, setterscript?: CompileResult) {
+ constructor(script: CompiledScript, setterscript?: CompiledScript) {
super();
if (script?.options.capturedVariables) {
const doc = Doc.assign(new Doc, script.options.capturedVariables);
this.captures = new ProxyField(doc);
}
- this.setterscript = setterscript?.compiled ? setterscript : undefined;
+ this.setterscript = setterscript;
this.script = script;
}
@@ -98,10 +98,10 @@ export class ScriptField extends ObjectField {
// }
[Copy](): ObjectField {
- return new ScriptField(this.script);
+ return new ScriptField(this.script, this.setterscript);
}
toString() {
- return `${this.script.originalScript}`;
+ return `${this.script.originalScript} + ${this.setterscript?.originalScript}`;
}
[ToScriptString]() {
@@ -141,22 +141,21 @@ export class ComputedField extends ScriptField {
[Copy](): ObjectField {
- return new ComputedField(this.script);
+ return new ComputedField(this.script, this.setterscript);
}
public static MakeScript(script: string, params: object = {}) {
const compiled = ScriptField.CompileScript(script, params, false);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }, setterScript?: string) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
- const setCompiled = setterScript ? ScriptField.CompileScript(setterScript, params, true, capturedVariables) : undefined;
- return compiled.compiled ? new ComputedField(compiled, setCompiled?.compiled ? setCompiled : undefined) : undefined;
+ return compiled.compiled ? new ComputedField(compiled) : undefined;
}
public static MakeInterpolated(fieldKey: string, interpolatorKey: string) {
const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
const setField = ScriptField.CompileScript(`(self['${fieldKey}-indexed'])[self.${interpolatorKey}] = value`, { value: "any" }, true, {});
- return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
+ return getField.compiled && setField.compiled ? new ComputedField(getField, setField) : undefined;
}
}
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 142f7e079..32f1b6e6c 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -12,7 +12,10 @@ export const documentSchema = createSchema({
links: listSpec(Doc), // computed (readonly) list of links associated with this document
// "Location" properties in a very general sense
- currentTimecode: "number", // current play back time of a temporal document (video / audio)
+ currentFrame: "number", // current frame of a frame based collection (e.g., a progressive slide)
+ lastFrame: "number", // last frame of a frame based collection (e.g., a progressive slide)
+ activeFrame: "number", // the active frame of a frame based animated document
+ urrentTimecode: "number", // current play back time of a temporal document (video / audio)
displayTimecode: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently
x: "number", // x coordinate when in a freeform view
@@ -77,7 +80,7 @@ export const documentSchema = createSchema({
isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee)
lockedPosition: "boolean", // whether the document can be moved (dragged)
_lockedTransform: "boolean",// whether a freeformview can pan/zoom
-
+
// drag drop properties
dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias", "copy", "move")
diff --git a/src/fields/util.ts b/src/fields/util.ts
index a287b0210..024c0f80e 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -116,7 +116,7 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
return true;
}
}
- if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript) {
+ if (target.__fields[prop] instanceof ComputedField && target.__fields[prop].setterscript && value !== undefined && !(value instanceof ComputedField)) {
return ScriptCast(target.__fields[prop])?.setterscript?.run({ self: target[SelfProxy], this: target[SelfProxy], value }).success ? true : false;
}
return _setter(target, prop, value, receiver);
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 231870531..b15042f9f 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -13,9 +13,7 @@ import { observable } from 'mobx';
import { Utils } from '../Utils';
import MobileInterface from './MobileInterface';
import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-
-
-
+import { resolvedPorts } from '../client/views/Main';
// const onPointerDown = (e: React.TouchEvent) => {
// let imgInput = document.getElementById("input_image_file");
@@ -106,10 +104,10 @@ class Uploader extends React.Component {
}
-// DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
+// DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "image upload");
(async () => {
const info = await CurrentUserUtils.loadCurrentUser();
- DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + "mobile");
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, info.email + "mobile");
await Docs.Prototypes.initialize();
if (info.id !== "__guest__") {
// a guest will not have an id registered
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index 01d2dfcad..c5f3ca717 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -246,7 +246,7 @@ async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hiera
if (typeof result === "string") {
let path: string;
let matches: RegExpExecArray | null;
- if ((matches = /\:1050\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
+ if ((matches = /\:\d+\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
// image already exists on our server
path = serverPathToFile(Directory.images, matches[1]);
} else {
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index c887aa4e6..2bf4c1956 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -15,7 +15,9 @@ const parse = require('pdf-parse');
import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from './ApiManagers/UploadManager';
import { red } from 'colors';
import { Stream } from 'stream';
+import { resolvedPorts } from './server_Initialization';
const requestImageSize = require("../client/util/request-image-size");
+import { resolvedServerUrl } from "./server_Initialization";
export enum SizeSuffix {
Small = "_s",
@@ -184,7 +186,7 @@ export namespace DashUploadUtils {
if (error !== null) {
return error;
}
- source = `http://localhost:1050${clientPathToFile(Directory.images, resolved)}`;
+ source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`;
}
let resolvedUrl: string;
/**
@@ -194,14 +196,14 @@ export namespace DashUploadUtils {
* basename subtree (i.e. /images/<some_guid>.<ext>) and put it on the end of the server's url.
*
* This can always be localhost, regardless of whether this is on the server or not, since we (the server, not the client)
- * will be the ones making the request, and from the perspective of dash-release or dash-web, localhost:1050 refers to the same thing
- * as the full dash-release.eastus.cloudapp.azure.com:1050.
+ * will be the ones making the request, and from the perspective of dash-release or dash-web, localhost:<port> refers to the same thing
+ * as the full dash-release.eastus.cloudapp.azure.com:<port>.
*/
const matches = isLocal().exec(source);
if (matches === null) {
resolvedUrl = source;
} else {
- resolvedUrl = `http://localhost:1050/${matches[1].split("\\").join("/")}`;
+ resolvedUrl = `${resolvedServerUrl}/${matches[1].split("\\").join("/")}`;
}
// See header comments: not all image files have exif data (I believe only JPG is the only format that can have it)
const exifData = await parseExifData(resolvedUrl);
diff --git a/src/server/index.ts b/src/server/index.ts
index ff74cfb33..590affd06 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -5,7 +5,7 @@ import * as path from 'path';
import { Database } from './database';
import { DashUploadUtils } from './DashUploadUtils';
import RouteSubscriber from './RouteSubscriber';
-import initializeServer from './server_Initialization';
+import initializeServer, { resolvedPorts } from './server_Initialization';
import RouteManager, { Method, _success, _permission_denied, _error, _invalid, PublicHandler } from './RouteManager';
import * as qs from 'query-string';
import UtilManager from './ApiManagers/UtilManager';
@@ -95,6 +95,11 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
secureHandler: ({ res }) => res.send(true)
});
+ addSupervisedRoute({
+ method: Method.GET,
+ subscription: "/resolvedPorts",
+ secureHandler: ({ res }) => res.send(resolvedPorts)
+ });
const serve: PublicHandler = ({ req, res }) => {
const detector = new mobileDetect(req.headers['user-agent'] || "");
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index 91a3cb6bf..7178add93 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -1,4 +1,5 @@
import { Database } from "./database";
+import { resolvedPorts } from "./server_Initialization";
//npx ts-node src/server/remapUrl.ts
@@ -34,7 +35,7 @@ async function update() {
if (url.href.includes("localhost") && url.href.includes("Bill")) {
dynfield = true;
- update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:1050${url.pathname}` };
+ update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:${resolvedPorts.server}${url.pathname}` };
}
}
}
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index d370385b2..744d4547b 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -20,8 +20,8 @@ import * as fs from 'fs';
import * as request from 'request';
import RouteSubscriber from './RouteSubscriber';
import { publicDirectory } from '.';
-import { logPort, pathFromRoot, } from './ActionUtilities';
-import { blue, yellow, red } from 'colors';
+import { logPort } from './ActionUtilities';
+import { blue, yellow } from 'colors';
import * as cors from "cors";
import { createServer, Server as HttpsServer } from "https";
import { Server as HttpServer } from "http";
@@ -32,6 +32,9 @@ import { SSL } from './apis/google/CredentialsLoader';
export type RouteSetter = (server: RouteManager) => void;
export let disconnect: Function;
+export let resolvedPorts: { server: number, socket: number } = { server: 1050, socket: 4321 };
+export let resolvedServerUrl: string;
+
export default async function InitializeServer(routeSetter: RouteSetter) {
const app = buildWithMiddleware(express());
@@ -55,17 +58,19 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
registerRelativePath(app);
let server: HttpServer | HttpsServer;
- const { serverPort } = process.env;
- const resolved = isRelease && serverPort ? Number(serverPort) : 1050;
+ const { serverPort, serverName } = process.env;
+ isRelease && serverPort && (resolvedPorts.server = Number(serverPort));
await new Promise<void>(resolve => server = isRelease ?
- createServer(SSL.Credentials, app).listen(resolved, resolve) :
- app.listen(resolved, resolve)
+ createServer(SSL.Credentials, app).listen(resolvedPorts.server, resolve) :
+ app.listen(resolvedPorts.server, resolve)
);
- logPort("server", resolved);
+ logPort("server", resolvedPorts.server);
+
+ resolvedServerUrl = `${isRelease && serverName ? `https://${serverName}.com` : "http://localhost"}:${resolvedPorts.server}`;
// initialize the web socket (bidirectional communication: if a user changes
// a field on one client, that change must be broadcast to all other clients)
- WebSocket.initialize(isRelease, app);
+ await WebSocket.initialize(isRelease, app);
disconnect = async () => new Promise<Error>(resolve => server.close(resolve));
return isRelease;
@@ -174,11 +179,11 @@ function registerRelativePath(server: express.Express) {
server.use("*", (req, res) => {
const relativeUrl = req.originalUrl;
if (!res.headersSent && req.headers.referer?.includes("corsProxy")) { // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here.
- const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:1050/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
- const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:1050/corsProxy/ )
+ const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart)
+ const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ )
const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : http:s//en.wikipedia.org/wiki/Engelbart)
const absoluteTargetBaseUrl = actualReferUrl.match(/http[s]?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org)
- const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:1050/corsProxy/https://en.wikipedia.org/<somethingelse>)
+ const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>)
res.redirect(redirectedProxiedUrl);
} else if (relativeUrl.startsWith("/search")) { // detect search query and use default search engine
res.redirect(req.headers.referer + "corsProxy/" + encodeURIComponent("http://www.google.com" + relativeUrl));
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index 21e772e83..d55c2e198 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -16,6 +16,7 @@ import executeImport from "../scraping/buxton/final/BuxtonImporter";
import { DocumentsCollection } from "./IDatabase";
import { createServer, Server } from "https";
import * as express from "express";
+import { resolvedPorts } from './server_Initialization';
export namespace WebSocket {
@@ -23,21 +24,21 @@ export namespace WebSocket {
const clients: { [key: string]: Client } = {};
export const socketMap = new Map<SocketIO.Socket, string>();
export let disconnect: Function;
- const defaultPort = 4321;
export async function initialize(isRelease: boolean, app: express.Express) {
let io: sio.Server;
- let resolved: number;
if (isRelease) {
const { socketPort } = process.env;
- resolved = socketPort ? Number(socketPort) : defaultPort;
+ if (socketPort) {
+ resolvedPorts.socket = Number(socketPort);
+ }
let socketEndpoint: Server;
- await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolved, resolve));
+ await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve));
io = sio(socketEndpoint!, SSL.Credentials as any);
} else {
- io = sio().listen(resolved = defaultPort);
+ io = sio().listen(resolvedPorts.socket);
}
- logPort("websocket", resolved);
+ logPort("websocket", resolvedPorts.socket);
console.log();
io.on("connection", function (socket: Socket) {
diff --git a/test/test.ts b/test/test.ts
index 5fc156b46..9dcd273af 100644
--- a/test/test.ts
+++ b/test/test.ts
@@ -2,7 +2,7 @@ import { expect } from 'chai';
import 'mocha';
const { JSDOM } = require('jsdom');
const dom = new JSDOM("", {
- url: "http://localhost:1050"
+ url: `http://localhost:${resolvedPorts.server}`
});
(global as any).window = dom.window;
@@ -12,6 +12,7 @@ import { Doc } from '../src/fields/Doc';
import { Cast } from '../src/fields/Types';
import { createSchema, makeInterface, defaultSpec } from '../src/fields/Schema';
import { ImageField } from '../src/fields/URLField';
+import { resolvedPorts } from '../src/client/views/Main';
describe("Document", () => {
it('should hold fields', () => {
const key = "Test";