diff options
35 files changed, 3726 insertions, 456 deletions
diff --git a/deploy/index.html b/deploy/index.html index 82e8c53a3..990ca510d 100644 --- a/deploy/index.html +++ b/deploy/index.html @@ -3,6 +3,7 @@ <head> <title>Dash Web</title> <link href="https://fonts.googleapis.com/css?family=Fjalla+One|Hind+Siliguri:300" rel="stylesheet"> + <link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500' rel='stylesheet'> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script> diff --git a/package-lock.json b/package-lock.json index f9e8bf582..19f10bb63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,36 @@ } } }, + "@babel/helper-annotate-as-pure": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "requires": { + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + } + } + }, "@babel/helper-function-name": { "version": "7.9.5", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", @@ -1217,6 +1247,11 @@ "@types/request": "*" } }, + "@types/reveal": { + "version": "3.3.33", + "resolved": "https://registry.npmjs.org/@types/reveal/-/reveal-3.3.33.tgz", + "integrity": "sha512-lKbezA9Oa5LfdSRwFDc/FHEGH4+FjiXh/a/PCSZAmN+KCeQJL/3ClOdAQwOxt3zdHc8XyioT+cNvIOletwRI7A==" + }, "@types/rimraf": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.4.tgz", @@ -2290,6 +2325,17 @@ "resolve": "^1.12.0" } }, + "babel-plugin-styled-components": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.7.tgz", + "integrity": "sha512-MBMHGcIA22996n9hZRf/UJLVVgkEOITuR2SvjHLb5dSTUyR4ZRGn+ngITapes36FI3WLxZHfRhkA1ffHxihOrg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + } + }, "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", @@ -2920,6 +2966,11 @@ } } }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "canvas": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz", @@ -4389,6 +4440,19 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, + "css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "requires": { + "tiny-invariant": "^1.0.6" + } + }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" + }, "css-in-js-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", @@ -4452,6 +4516,16 @@ } } }, + "css-to-react-native": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.2.tgz", + "integrity": "sha512-VOFaeZA053BqvvvqIA8c9n0+9vFppVBAHCp6JgFTtTMU3Mzi+XnelJ9XC9ul3BqFzZyQ5N+H0SnwsWT2Ebchxw==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^3.3.0" + } + }, "css-vendor": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", @@ -7858,6 +7932,11 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "is-what": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.10.0.tgz", + "integrity": "sha512-U4RYCXNOmATQHlOPlOCHCfXyKEFIPqvyaKDqYRuLbD6EYKcTTfc3YXkAYjzOVxO3zt34L+Wh2feIyWrYiZ7kng==" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8775,6 +8854,14 @@ "trim-newlines": "^1.0.0" } }, + "merge-anything": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-2.4.4.tgz", + "integrity": "sha512-l5XlriUDJKQT12bH+rVhAHjwIuXWdAIecGwsYjv2LJo+dA1AeRTmeQS+3QBpO6lEthBMDi2IUMpLC1yyRvGlwQ==", + "requires": { + "is-what": "^3.3.1" + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -13541,8 +13628,7 @@ "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" }, "prebuild-install": { "version": "5.3.3", @@ -14033,6 +14119,11 @@ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, + "raf-schd": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz", + "integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==" + }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -14156,6 +14247,20 @@ "pure-color": "^1.2.0" } }, + "react-beautiful-dnd": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz", + "integrity": "sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg==", + "requires": { + "@babel/runtime": "^7.8.4", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.1.1", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + } + }, "react-color": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.18.1.tgz", @@ -14388,6 +14493,18 @@ } } }, + "react-redux": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz", + "integrity": "sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==", + "requires": { + "@babel/runtime": "^7.5.5", + "hoist-non-react-statics": "^3.3.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.9.0" + } + }, "react-resizable": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-1.10.1.tgz", @@ -14397,6 +14514,19 @@ "react-draggable": "^4.0.3" } }, + "react-resizable-rotatable-draggable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/react-resizable-rotatable-draggable/-/react-resizable-rotatable-draggable-0.2.0.tgz", + "integrity": "sha512-F8TPx3z7/AcmRViySbYV3LpUWXFpHlGAmKmNcYMgPlS+h1eYFazRG3xYS8Z6e48hWY1EcCny/YNrwRNUrap8CQ==" + }, + "react-reveal": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/react-reveal/-/react-reveal-1.2.2.tgz", + "integrity": "sha512-JCv3fAoU6Z+Lcd8U48bwzm4pMZ79qsedSXYwpwt6lJNtj/v5nKJYZZbw3yhaQPPgYePo3Y0NOCoYOq/jcsisuw==", + "requires": { + "prop-types": "^15.5.10" + } + }, "react-select": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-3.1.0.tgz", @@ -14614,6 +14744,15 @@ "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=" }, + "redux": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", + "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + }, "regenerator-runtime": { "version": "0.13.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", @@ -14862,6 +15001,11 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reveal.js": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-4.0.2.tgz", + "integrity": "sha512-LWZSUenufF1gpD7npxJ7KfoQQFKgc1D6XrLTFgKlwWNP1BQ74hT48KONFWMAw+8R/QUeaScCLCLrBVHDfFIEiw==" + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -16205,6 +16349,36 @@ } } }, + "styled-components": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.4.1.tgz", + "integrity": "sha512-RNqj14kYzw++6Sr38n7197xG33ipEOktGElty4I70IKzQF1jzaD1U4xQ+Ny/i03UUhHlC5NWEO+d8olRCDji6g==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@emotion/is-prop-valid": "^0.8.1", + "@emotion/unitless": "^0.7.0", + "babel-plugin-styled-components": ">= 1", + "css-to-react-native": "^2.2.2", + "memoize-one": "^5.0.0", + "merge-anything": "^2.2.4", + "prop-types": "^15.5.4", + "react-is": "^16.6.0", + "stylis": "^3.5.0", + "stylis-rule-sheet": "^0.0.10", + "supports-color": "^5.5.0" + } + }, + "stylis": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", + "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==" + }, + "stylis-rule-sheet": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", + "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -16513,6 +16687,11 @@ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -17398,6 +17577,11 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-memo-one": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz", + "integrity": "sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==" + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", diff --git a/package.json b/package.json index 553a28b94..47aea960f 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "@hig/theme-data": "^2.16.1", "@material-ui/core": "^4.11.0", "@types/google-maps": "^3.2.2", + "@types/reveal": "^3.3.33", "@types/webscopeio__react-textarea-autocomplete": "^4.6.1", "@webscopeio/react-textarea-autocomplete": "^4.7.0", "adm-zip": "^0.4.16", @@ -217,6 +218,7 @@ "rc-switch": "^1.9.0", "react": "^16.12.0", "react-autosuggest": "^9.4.3", + "react-beautiful-dnd": "^13.0.0", "react-color": "^2.18.1", "react-compound-slider": "^2.5.0", "react-datepicker": "^3.0.0", @@ -227,11 +229,14 @@ "react-loading": "^2.0.3", "react-measure": "^2.2.4", "react-resizable": "^1.10.1", + "react-resizable-rotatable-draggable": "^0.2.0", + "react-reveal": "^1.2.2", "react-select": "^3.1.0", "react-table": "^6.11.5", "readline": "^1.3.0", "request": "^2.88.0", "request-promise": "^4.2.5", + "reveal.js": "^4.0.2", "rimraf": "^3.0.0", "serializr": "^1.5.4", "sharp": "^0.23.4", @@ -240,6 +245,7 @@ "socket.io-client": "^2.3.0", "solr-node": "^1.2.1", "standard-http-error": "^2.0.1", + "styled-components": "^4.4.1", "textarea-caret": "^3.1.0", "typescript-collections": "^1.3.3", "typescript-language-server": "^0.4.0", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b809a73b7..7719b3eef 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -33,6 +33,7 @@ import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; import { DocHolderBox } from "../views/nodes/DocHolderBox"; import { FontIconBox } from "../views/nodes/FontIconBox"; +import { MenuIconBox } from "../views/nodes/MenuIconBox"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; import { KeyValueBox } from "../views/nodes/KeyValueBox"; @@ -128,6 +129,7 @@ export interface DocumentOptions { isLinkButton?: boolean; _columnWidth?: number; _fontSize?: string; + _fontWeight?: number; _fontFamily?: string; curPage?: number; currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds @@ -135,6 +137,12 @@ export interface DocumentOptions { 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 + appearFrame?: number; // the frame in which the document appears + presTransition?: number; //the time taken for the transition TO a document + presDuration?: number; //the duration of the slide in presentation view + presProgressivize?: boolean; + // xArray?: number[]; + // yArray?: number[]; borderRounding?: string; boxShadow?: string; dontRegisterChildViews?: boolean; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ba97b9fe0..1f140e145 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -770,13 +770,13 @@ export class CurrentUserUtils { } static setupSidebarContainer(doc: Doc) { - if (doc["sidebar"] === undefined) { + if (doc.sidebar === undefined) { const sidebarContainer = new Doc(); sidebarContainer._chromeStatus = "disabled"; sidebarContainer.onClick = ScriptField.MakeScript("freezeSidebar()"); - doc["sidebar"] = new PrefetchProxy(sidebarContainer); + doc.sidebar = new PrefetchProxy(sidebarContainer); } - return doc["sidebar"] as Doc; + return doc.sidebar as Doc; } // setup the list of sidebar mode buttons which determine what is displayed in the sidebar diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 523dbfca0..d18eac374 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -127,6 +127,10 @@ export class DocumentManager { CollectionDockingView.AddRightSplit(doc); finished?.(); } + // static openInPlace = (doc: Doc, finished?: () => void) => { + // CollectionDockingView.AddTab(doc); + // finished?.(); + // } public jumpToDocument = async ( targetDoc: Doc, // document to display willZoom: boolean, // whether to zoom doc to take up most of screen @@ -176,7 +180,7 @@ export class DocumentManager { const targetDocContextView = getFirstDocView(targetDocContext); targetDocContext._scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. - targetDocContext._viewTransition = "transform 500ms"; + targetDocContext._viewTransition = "transform 10000ms"; targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); // now find the target document within the context diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store Binary files differindex 3717a2923..3ce88292c 100644 --- a/src/client/views/.DS_Store +++ b/src/client/views/.DS_Store diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 6c5eda256..4a77728b6 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -115,9 +115,9 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"), "none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true); - var controlPoints: { X: number, Y: number, I: number }[] = []; - var handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = []; - var handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = []; + const controlPoints: { X: number, Y: number, I: number }[] = []; + const handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = []; + const handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = []; if (data.length >= 4) { for (var i = 0; i <= data.length - 4; i += 4) { controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i }); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b531ba0b8..845e93ecb 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -82,8 +82,7 @@ export class MainView extends React.Component { @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } @computed public get searchDoc() { return Cast(this.userDoc["search-panel"], Doc) as Doc; } - sidebar: string = "sidebar"; - @observable public sidebarContent: any = this.userDoc?.[this.sidebar]; + @observable public sidebarContent: any = this.userDoc?.sidebar; @observable public panelContent: string = "none"; @observable public showProperties: boolean = false; public isPointerDown = false; @@ -177,6 +176,9 @@ export class MainView extends React.Component { fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper, fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft); + //Pres trail icons (just for the sake of merging) + library.add(fa.faAngleUp, fa.faAngleDown, fa.faPlayCircle, fa.faClock, + fa.faRocket, fa.faExchangeAlt, faBuffer); this.initEventListeners(); this.initAuthenticationRouters(); } diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 738dbc75b..29afd447f 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -29,6 +29,7 @@ import { ImageField } from '../../fields/URLField'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { DocumentType } from '../documents/DocumentTypes'; import { InkField } from '../../fields/InkField'; +import { PresBox } from './nodes/PresBox'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -215,9 +216,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { get pinButton() { const targetDoc = this.selectedDoc; const isPinned = targetDoc && Doc.isDocPinned(targetDoc); - return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : - "Pin to presentation"}</div></>} placement="top"> + return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top"> <div> <div className="propertiesButtons-linker" style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white" }} @@ -236,6 +235,37 @@ export class PropertiesButtons extends React.Component<{}, {}> { } @computed + get pinWithViewButton() { + const targetDoc = this.selectedDoc; + const isPinned = targetDoc && Doc.isDocPinned(targetDoc); + if (targetDoc) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + } + return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin with this view"}</div></>}> + <div className="propertiesButtons-linker" + style={{ backgroundColor: "white", color: "black" }} + onClick={e => { + if (targetDoc) { + DockedFrameRenderer.PinDoc(targetDoc, false); + const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeDoc.presPinView = true; + activeDoc.presPinViewX = x; + activeDoc.presPinViewY = y; + activeDoc.presPinViewScale = scale; + } + }}> + <div style={{ position: 'absolute', fontSize: 25, fontWeight: 700, transform: 'translate(42%, -7px)', color: 'rgba(0,0,0,0.2)' }}>V</div> + <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> + </div></Tooltip>; + } + + + @computed get metadataButton() { //const view0 = this.view0; if (this.selectedDoc) { @@ -678,6 +708,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { {this.pinButton} </div> <div className="propertiesButtons-button"> + {this.pinWithViewButton} + </div> + <div className="propertiesButtons-button"> {this.copyButton} </div> <div className="propertiesButtons-button"> diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 6ebd5103b..98babc3d2 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -20,6 +20,67 @@ } } +.miniPres:hover { + opacity: 1; +} + +.miniPres { + position: absolute; + overflow: hidden; + right: 10; + top: 10; + opacity: 0.1; + transition: all 0.4s; + /* border: solid 1px; */ + color: white; + /* box-shadow: black 0.4vw 0.4vw 0.8vw; */ + + .miniPresOverlay { + display: grid; + grid-template-columns: auto auto auto auto auto auto auto auto; + grid-template-rows: 100%; + height: 100%; + justify-items: center; + align-items: center; + + .miniPres-button-text { + display: flex; + height: 20; + font-weight: 400; + min-width: 100%; + border-radius: 5px; + align-items: center; + justify-content: center; + transition: all 0.3s; + } + + .miniPres-divider { + width: 0.5px; + height: 80%; + border-right: solid 2px #5a5a5a; + } + + .miniPres-button { + display: flex; + height: 20; + min-width: 20; + border-radius: 100%; + align-items: center; + justify-content: center; + transition: all 0.3s; + } + + .miniPres-button:hover { + background-color: #5a5a5a; + } + + .miniPres-button-text:hover { + background-color: #5a5a5a; + } + } +} + + .lm_title { margin-top: 3px; border-radius: 5px; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 75a4bda14..6d4a7f311 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -30,6 +30,8 @@ import { SnappingManager } from '../../util/SnappingManager'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { listSpec } from '../../../fields/Schema'; import { clamp } from 'lodash'; +import { PresBox } from '../nodes/PresBox'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { InteractionUtils } from '../../util/InteractionUtils'; import { InkTool } from '../../../fields/InkField'; const _global = (window /* browser */ || global /* node */) as any; @@ -862,6 +864,27 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { return false; }), emptyFunction, emptyFunction); } + getCurrentFrame = (): number => { + const presTargetDoc = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null); + const currentFrame = Cast(presTargetDoc.currentFrame, "number", null); + return currentFrame; + } + renderMiniPres() { + return <div className="miniPres" style={{ + width: 250, height: 30, background: '#323232' + }}> + <div className="miniPresOverlay" > + <div className="miniPres-button" onClick={PresBox.Instance.back}><FontAwesomeIcon icon={"arrow-left"} /></div> + <div className="miniPres-button" onClick={() => PresBox.Instance.startOrResetPres(PresBox.Instance.itemIndex)}><FontAwesomeIcon icon={PresBox.Instance.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> + <div className="miniPres-button" onClick={PresBox.Instance.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="miniPres-divider"></div> + <div className="miniPres-button-text">Slide {PresBox.Instance.itemIndex + 1} / {PresBox.Instance.childDocs.length}</div> + {/* <div className="miniPres-button-text">{this.getCurrentFrame}</div> */} + <div className="miniPres-divider"></div> + <div className="miniPres-button-text" onClick={PresBox.Instance.updateMinimize}>EXIT</div> + </div> + </div>; + } renderMiniMap() { return <div className="miniMap" style={{ width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor, @@ -944,6 +967,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)} + {document._viewType === CollectionViewType.Freeform && this._document?.miniPres ? this.renderMiniPres() : (null)} </>; } diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index 0a316317f..b41cbe92d 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -368,6 +368,7 @@ } } + .numKeyframe { flex-direction: column; padding-top: 5px; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index cca78cf9f..fe3d57bdb 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -184,7 +184,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); - smoothScroll(500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll(doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop); } afterFocus && setTimeout(() => { if (afterFocus?.()) { } @@ -287,19 +287,31 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) } }); if (super.onInternalDrop(e, de)) { - const newDoc = de.complete.docDragData.droppedDocuments[0]; + const newDocs = de.complete.docDragData.droppedDocuments; const docs = this.childDocList; if (docs) { - if (targInd === -1) targInd = docs.length; - else targInd = docs.indexOf(this.filteredChildren[targInd]); - const srcInd = docs.indexOf(newDoc); - docs.splice(srcInd, 1); - docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, newDoc); + newDocs.map((doc, i) => { + console.log(doc.title); + if (i === 0) { + if (targInd === -1) targInd = docs.length; + else targInd = docs.indexOf(this.filteredChildren[targInd]); + const srcInd = docs.indexOf(doc); + docs.splice(srcInd, 1); + docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc); + } else if (i < (newDocs.length / 2)) { //glr: for some reason dragged documents are duplicated + if (targInd === -1) targInd = docs.length; + else targInd = docs.indexOf(newDocs[0]) + 1; + const srcInd = docs.indexOf(doc); + docs.splice(srcInd, 1); + docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc); + } + }); } } } return false; } + @undoBatch @action onExternalDrop = async (e: React.DragEvent): Promise<void> => { diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index fb75b8b61..f193a9787 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -12,6 +12,7 @@ import { NumCast, StrCast, Cast } from "../../../fields/Types"; import { ImageField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import { DragManager } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; @@ -346,7 +347,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC </div> : (null); for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `; const chromeStatus = this.props.parent.props.Document._chromeStatus; - + const type = this.props.parent.props.Document.type; return <> {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView} { @@ -366,7 +367,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC {this.props.parent.children(this.props.docList, uniqueHeadings.length)} {singleColumn ? (null) : this.props.parent.columnDragger} </div> - {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? + {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled' && type !== DocumentType.PRES) ? <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" style={{ width: style.columnWidth / style.numGroupColumns, marginBottom: 70 }}> <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} /> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 92aee3776..2b07c4efb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -13,16 +13,153 @@ } .collectionfreeformview-viewdef { - > .collectionFreeFormDocumentView-container { + >.collectionFreeFormDocumentView-container { pointer-events: none; + .contentFittingDocumentDocumentView-previewDoc { pointer-events: all; } } + + svg.presPaths { + position: absolute; + z-index: 100000; + overflow: visible; + } + + svg.presPaths-hidden { + display: none; + } } .collectionfreeformview-none { touch-action: none; + + svg.presPaths { + position: absolute; + z-index: 100000; + overflow: visible; + } + + svg.presPaths-hidden { + display: none; + } +} + +.pathOrder { + position: absolute; + z-index: 200000; + + .pathOrder-frame { + position: absolute; + width: 40; + text-align: center; + font-size: 30; + background-color: #69a6db; + font-family: Roboto; + font-weight: 300; + } +} + +.progressivizeButton { + position: absolute; + display: grid; + grid-template-columns: auto 20px auto; + transform: translate(-105%, 0); + align-items: center; + border: black solid 1px; + border-radius: 3px; + justify-content: center; + width: 40; + z-index: 30000; + height: 20; + overflow: hidden; + background-color: #d5dce2; + transition: all 1s; + + .progressivizeButton-prev:hover { + color: #5a9edd; + } + + .progressivizeButton-frame { + justify-self: center; + text-align: center; + width: 15px; + } + + .progressivizeButton-next:hover { + color: #5a9edd; + } +} + +.resizable { + background: rgba(0, 0, 0, 0.2); + width: 100px; + height: 100px; + position: absolute; + top: 100px; + left: 100px; + + .resizers { + width: 100%; + height: 100%; + border: 3px solid #69a6db; + box-sizing: border-box; + + .resizer { + position: absolute; + width: 10px; + height: 10px; + border-radius: 50%; + /*magic to turn square into circle*/ + background: white; + border: 3px solid #69a6db; + } + + .resizer.top-left { + left: -3px; + top: -3px; + cursor: nwse-resize; + /*resizer cursor*/ + } + + .resizer.top-right { + right: -3px; + top: -3px; + cursor: nesw-resize; + } + + .resizer.bottom-left { + left: -3px; + bottom: -3px; + cursor: nesw-resize; + } + + .resizer.bottom-right { + right: -3px; + bottom: -3px; + cursor: nwse-resize; + } + } +} + +.progressivizeMove-frame { + width: 20px; + border-radius: 2px; + z-index: 100000; + color: white; + text-align: center; + background-color: #5a9edd; + transform: translate(-110%, 110%); +} + +.progressivizeButton:hover { + box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5); + + .progressivizeButton-frame { + background-color: #5a9edd; + color: white; + } } .collectionFreeform-customText { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ee6ca1a22..f2ad0ba58 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc"; @@ -46,6 +46,7 @@ import "./CollectionFreeFormView.scss"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { PresBox } from "../../nodes/PresBox"; import { SearchUtil } from "../../../util/SearchUtil"; import { LinkManager } from "../../../util/LinkManager"; @@ -76,8 +77,7 @@ export type collectionFreeformViewProps = { forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox) viewDefDivClick?: ScriptField; childPointerEvents?: boolean; - scaleField?: string; // used by formattedTextBox when displaying a sidebar freeform view which needs its own scale field - noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) + scaleField?: string; }; @observer @@ -213,7 +213,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const layoutDoc = Doc.Layout(d); if (this.Document.currentFrame !== undefined) { const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); - CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.opacity); + CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, vals.opacity); } else { d.x = x + NumCast(d.x) - dropPos[0]; d.y = y + NumCast(d.y) - dropPos[1]; @@ -911,7 +911,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P // !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1); // } else { if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) { - if (!doc.z) this.setPan(newPanX, newPanY, "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow + // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... + if (!doc.z) this.setPan(newPanX, newPanY, doc.presTransition || doc.presTransition === 0 ? `transform ${doc.presTransition}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow } Doc.BrushDoc(this.props.Document); this.props.focus(this.props.Document); @@ -954,8 +955,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P DataDoc: childData, Document: childLayout, LibraryPath: this.libraryPath, - LayoutTemplate: childLayout.z ? undefined : this.props.ChildLayoutTemplate, - LayoutTemplateString: childLayout.z ? undefined : this.props.ChildLayoutString, + LayoutTemplate: this.props.ChildLayoutTemplate, + LayoutTemplateString: this.props.ChildLayoutString, FreezeDimensions: this.props.freezeChildDimensions, layoutKey: undefined, setupDragLines: this.setupDragLines, @@ -1399,6 +1400,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} + presPaths={BoolCast(this.Document.presPathView)} + progressivize={BoolCast(this.Document.editProgressivize)} + zoomProgressivize={BoolCast(this.Document.editZoomProgressivize)} transition={Cast(this.layoutDoc._viewTransition, "string", null)} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> @@ -1439,7 +1443,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }}> {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? this.placeholder : this.marqueeView} - {!this.props.noOverlay ? <CollectionFreeFormOverlayView elements={this.elementFunc} /> : (null)} + <CollectionFreeFormOverlayView elements={this.elementFunc} /> <div className={"pullpane-indicator"} style={{ @@ -1482,11 +1486,69 @@ interface CollectionFreeFormViewPannableContentsProps { viewDefDivClick?: ScriptField; children: () => JSX.Element[]; transition?: string; + presPaths?: boolean; + progressivize?: boolean; + zoomProgressivize?: boolean; } @observer class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{ + @computed get zoomProgressivize() { + if (this.props.zoomProgressivize) { + console.log("should render"); + return ( + <> + {PresBox.Instance.zoomProgressivizeContainer} + </> + ); + } + } + + @computed get progressivize() { + if (this.props.progressivize) { + console.log("should render"); + return ( + <> + {PresBox.Instance.progressivizeChildDocs} + </> + ); + } + } + + @computed get presPaths() { + const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden"); + if (this.props.presPaths) { + return ( + <> + <div>{PresBox.Instance.order}</div> + <svg className={presPaths}> + <defs> + <marker id="arrow" markerWidth="3" overflow="visible" markerHeight="3" refX="5" refY="5" orient="auto" markerUnits="strokeWidth"> + <path d="M0,0 L0,6 L9,3 z" fill="#69a6db" /> + </marker> + <marker id="square" markerWidth="3" markerHeight="3" overflow="visible" + refX="5" refY="5" orient="auto" markerUnits="strokeWidth"> + <path d="M 5,1 L 9,5 5,9 1,5 z" fill="#69a6db" /> + </marker> + <marker id="markerSquare" markerWidth="7" markerHeight="7" refX="4" refY="4" + orient="auto" overflow="visible"> + <rect x="1" y="1" width="5" height="5" fill="#69a6db" /> + </marker> + + <marker id="markerArrow" markerWidth="5" markerHeight="5" refX="2" refY="7" + orient="auto" overflow="visible"> + <path d="M2,2 L2,13 L8,7 L2,2" fill="#69a6db" /> + </marker> + </defs>; + {PresBox.Instance.paths} + </svg> + </> + ); + } + } + render() { + // trace(); const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none"); const cenx = this.props.centeringShiftX(); const ceny = this.props.centeringShiftY(); @@ -1499,6 +1561,9 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF transition: this.props.transition }}> {this.props.children()} + {this.presPaths} + {this.progressivize} + {this.zoomProgressivize} </div>; } } diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss index 71b6d3ce9..5b41db90e 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss @@ -5,8 +5,8 @@ font-family: "Noto Sans"; cursor: auto; - overflow-x: visible; - overflow-y: visible; + overflow-x: hidden; + overflow-y: scroll; .propertiesView-title { background-color: rgb(159, 159, 159); @@ -567,6 +567,27 @@ } } +.propertiesView-presSelected { + border-top: solid 1px darkgrey; + width: 100%; + padding-top: 5px; + font-family: Roboto; + font-weight: 500; + display: inline-flex; + + .propertiesView-selectedList { + border-left: solid 1px darkgrey; + margin-left: 10px; + padding-left: 5px; + + .selectedList-items { + font-size: 12; + font-weight: 300; + margin-top: 1; + } + } +} + .widthAndDash { .width { diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx index 0c7111a1d..89f48fc65 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx @@ -27,6 +27,8 @@ import { ColorState, SketchPicker } from "react-color"; import AntimodeMenu from "../../AntimodeMenu"; import "./FormatShapePane.scss"; import { discovery_v1 } from "googleapis"; +import { PresBox } from "../../nodes/PresBox"; +import { DocumentManager } from "../../../util/DocumentManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -49,8 +51,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @computed get selectedDocumentView() { if (SelectionManager.SelectedDocuments().length) { return SelectionManager.SelectedDocuments()[0]; + } else if (PresBox.Instance._selectedArray.length >= 1) { + return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); } else { return undefined; } } + @computed get isPres(): boolean { + if (this.selectedDoc?.type === DocumentType.PRES) return true; + return false; + } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } @computed get dataDoc() { return this.selectedDocumentView?.dataDoc; } @@ -62,6 +70,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @observable openLayout: boolean = true; @observable openAppearance: boolean = true; @observable openTransform: boolean = true; + //Pres Trails booleans: + @observable openAddSlide: boolean = true; + @observable openPresentationTools: boolean = true; + @observable openPresTransitions: boolean = true; + @observable openPresProgressivize: boolean = true; + @observable openSlideOptions: boolean = true; @observable inActions: boolean = false; @observable _controlBtn: boolean = false; @@ -407,15 +421,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { var index = 0; if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { doc.rotation = Number(doc.rotation) + Number(angle); - const ink = Cast(doc.data, InkField)?.inkData; - if (ink) { - + const inks = Cast(doc.data, InkField)?.inkData; + if (inks) { const newPoints: { X: number, Y: number }[] = []; - for (var i = 0; i < ink.length; i++) { - const newX = Math.cos(angle) * (ink[i].X - _centerPoints[index].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].X; - const newY = Math.sin(angle) * (ink[i].X - _centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].Y; + inks.forEach(ink => { + const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X; + const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y; newPoints.push({ X: newX, Y: newY }); - } + }); doc.data = new InkField(newPoints); const xs = newPoints.map(p => p.X); const ys = newPoints.map(p => p.Y); @@ -542,7 +555,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { getField(key: string) { //if (this.selectedDoc) { - return Field.toString(this.selectedDoc![key] as Field); + return Field.toString(this.selectedDoc?.[key] as Field); // } else { // return undefined as Opt<string>; // } @@ -692,7 +705,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <input className="width-range" type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={(action((e) => this.widthStk = e.target.value))} - onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo");; }} + onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }} onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }} /> </div> @@ -743,123 +756,207 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { render() { - if (!this.selectedDoc) { + if (!this.selectedDoc && !this.isPres) { return <div className="propertiesView" style={{ width: this.props.width }}> <div className="propertiesView-title" style={{ width: this.props.width }}> No Document Selected - </div> + </div> </div>; } else { - const novice = Doc.UserDoc().noviceMode; - return <div className="propertiesView" style={{ - width: this.props.width, - // overflowY: this.inActions ? "visible" : "scroll" - }} > - <div className="propertiesView-title" style={{ width: this.props.width }}> - Properties - {/* <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> - <FontAwesomeIcon icon="times" color="black" size="sm" /> - </div> */} - </div> - <div className="propertiesView-name"> - {this.editableTitle} - </div> - <div className="propertiesView-settings" onPointerEnter={() => runInAction(() => { this.inActions = true; })} - onPointerLeave={() => runInAction(() => { this.inActions = false; })}> - <div className="propertiesView-settings-title" - onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })} - style={{ backgroundColor: this.openActions ? "black" : "" }}> - Actions - <div className="propertiesView-settings-title-icon"> - <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" /> + if (this.selectedDoc && !this.isPres) { + return <div className="propertiesView" style={{ + width: this.props.width, + // overflowY: this.inActions ? "visible" : "scroll" + }} > + <div className="propertiesView-title" style={{ width: this.props.width }}> + Properties + {/* <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> + <FontAwesomeIcon icon="times" color="black" size="sm" /> + </div> */} + </div> + <div className="propertiesView-name"> + {this.editableTitle} + </div> + <div className="propertiesView-settings" onPointerEnter={() => runInAction(() => { this.inActions = true; })} + onPointerLeave={action(() => this.inActions = false)}> + <div className="propertiesView-settings-title" + onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })} + style={{ backgroundColor: this.openActions ? "black" : "" }}> + Actions + <div className="propertiesView-settings-title-icon"> + <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {!this.openActions ? (null) : + <div className="propertiesView-settings-content"> + <PropertiesButtons /> + </div>} </div> - {!this.openActions ? (null) : - <div className="propertiesView-settings-content"> - <PropertiesButtons /> - </div>} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} - style={{ backgroundColor: this.openSharing ? "black" : "" }}> - Sharing {"&"} Permissions - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} + style={{ backgroundColor: this.openSharing ? "black" : "" }}> + Sharing {"&"} Permissions + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {!this.openSharing ? (null) : + <div className="propertiesView-sharing-content"> + {this.sharingTable} + </div>} </div> - {!this.openSharing ? (null) : - <div className="propertiesView-sharing-content"> - {this.sharingTable} + + {!this.isInk ? (null) : + <div className="propertiesView-appearance"> + <div className="propertiesView-appearance-title" + onPointerDown={() => runInAction(() => { this.openAppearance = !this.openAppearance; })} + style={{ backgroundColor: this.openAppearance ? "black" : "" }}> + Appearance + <div className="propertiesView-appearance-title-icon"> + <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {!this.openAppearance ? (null) : + <div className="propertiesView-appearance-content"> + {this.appearanceEditor} + </div>} </div>} - </div> - {!this.isInk ? (null) : - <div className="propertiesView-appearance"> - <div className="propertiesView-appearance-title" - onPointerDown={() => runInAction(() => { this.openAppearance = !this.openAppearance; })} - style={{ backgroundColor: this.openAppearance ? "black" : "" }}> - Appearance - <div className="propertiesView-appearance-title-icon"> - <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" /> + {this.isInk ? <div className="propertiesView-transform"> + <div className="propertiesView-transform-title" + onPointerDown={() => runInAction(() => { this.openTransform = !this.openTransform; })} + style={{ backgroundColor: this.openTransform ? "black" : "" }}> + Transform + <div className="propertiesView-transform-title-icon"> + <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {this.openTransform ? <div className="propertiesView-transform-content"> + {this.transformEditor} + </div> : null} + </div> : null} + + <div className="propertiesView-fields"> + <div className="propertiesView-fields-title" + onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })} + style={{ backgroundColor: this.openFields ? "black" : "" }}> + <div className="propertiesView-fields-title-name"> + Fields {"&"} Tags + <div className="propertiesView-fields-title-icon"> + <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> </div> - {!this.openAppearance ? (null) : - <div className="propertiesView-appearance-content"> - {this.appearanceEditor} + {!novice && this.openFields ? <div className="propertiesView-fields-checkbox"> + {this.fieldsCheckbox} + <div className="propertiesView-fields-checkbox-text">Layout</div> + </div> : null} + {!this.openFields ? (null) : + <div className="propertiesView-fields-content"> + {novice ? this.noviceFields : this.expandedField} </div>} - </div>} - - {this.isInk ? <div className="propertiesView-transform"> - <div className="propertiesView-transform-title" - onPointerDown={() => runInAction(() => { this.openTransform = !this.openTransform; })} - style={{ backgroundColor: this.openTransform ? "black" : "" }}> - Transform - <div className="propertiesView-transform-title-icon"> - <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + <div className="propertiesView-layout"> + <div className="propertiesView-layout-title" + onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })} + style={{ backgroundColor: this.openLayout ? "black" : "" }}> + Layout + <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}> + <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null} </div> - {this.openTransform ? <div className="propertiesView-transform-content"> - {this.transformEditor} - </div> : null} - </div> : null} - - <div className="propertiesView-fields"> - <div className="propertiesView-fields-title" - onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })} - style={{ backgroundColor: this.openFields ? "black" : "" }}> - <div className="propertiesView-fields-title-name"> - Fields {"&"} Tags - <div className="propertiesView-fields-title-icon"> - <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div>; + } + if (this.isPres) { + return <div className="propertiesView" style={{ width: this.props.width }} > + <div className="propertiesView-title" style={{ width: this.props.width }}> + Presentation + <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> + <FontAwesomeIcon icon="times" color="black" size="sm" /> + </div> + </div> + <div className="propertiesView-name"> + {this.editableTitle} + <div className="propertiesView-presSelected"> + {PresBox.Instance._selectedArray.length} selected + <div className="propertiesView-selectedList"> + {PresBox.Instance.listOfSelected} </div> </div> </div> - {!novice && this.openFields ? <div className="propertiesView-fields-checkbox"> - {this.fieldsCheckbox} - <div className="propertiesView-fields-checkbox-text">Layout</div> - </div> : null} - {!this.openFields ? (null) : - <div className="propertiesView-fields-content"> - {novice ? this.noviceFields : this.expandedField} - </div>} - </div> - <div className="propertiesView-layout"> - <div className="propertiesView-layout-title" - onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })} - style={{ backgroundColor: this.openLayout ? "black" : "" }}> - Layout - <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}> - <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-settings"> + <div className="propertiesView-settings-title" + onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })} + style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> + <FontAwesomeIcon icon={"plus"} /> Add new slide + <div className="propertiesView-settings-title-icon"> + <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openAddSlide ? <div className="propertiesView-settings-content"> + {PresBox.Instance.newDocumentDropdown} + </div> : null} </div> - {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null} - </div> - </div>; - + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })} + style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> + <FontAwesomeIcon icon={"rocket"} /> Transitions + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {this.openPresTransitions ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.transitionDropdown} + </div> : null} + </div> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })} + style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> + <FontAwesomeIcon icon={"tasks"} /> Progressivize + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {this.openPresProgressivize ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.progressivizeDropdown} + </div> : null} + </div> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })} + style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> + <FontAwesomeIcon icon={"cog"} /> {PresBox.Instance.stringType} options + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {this.openSlideOptions ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.optionsDropdown} + </div> : null} + </div> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} + style={{ backgroundColor: this.openSharing ? "black" : "" }}> + Sharing {"&"} Permissions + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {this.openSharing ? <div className="propertiesView-sharing-content"> + {this.sharingTable} + </div> : null} + </div> + </div>; + } } } -}
\ No newline at end of file +}
\ No newline at end of file diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 5c921cea4..2396e6973 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -43,7 +43,7 @@ const AudioDocument = makeInterface(documentSchema, audioSchema); export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument>(AudioDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } public static Enabled = false; - + static Instance: AudioBox; _linkPlayDisposer: IReactionDisposer | undefined; _reactionDisposer: IReactionDisposer | undefined; _scrubbingDisposer: IReactionDisposer | undefined; @@ -51,12 +51,14 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument _recorder: any; _recordStart = 0; _stream: MediaStream | undefined; - + constructor(props: any) { + super(props); + AudioBox.Instance = this; + } @observable private static _scrubTime = 0; @computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); } set audioState(value) { this.dataDoc.audioState = value; } public static SetScrubTime = (timeInMillisFrom1970: number) => { runInAction(() => AudioBox._scrubTime = 0); runInAction(() => AudioBox._scrubTime = timeInMillisFrom1970); }; - @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); } async slideTemplate() { return (await Cast((await Cast(Doc.UserDoc().slidesBtn, Doc) as Doc).dragFactory, Doc) as Doc); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index ce39c3735..42a42ddf1 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -11,10 +11,12 @@ import { Document } from "../../../fields/documentSchemas"; import { TraceMobx } from "../../../fields/util"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; import { List } from "../../../fields/List"; -import { numberRange } from "../../../Utils"; +import { numberRange, smoothScroll } from "../../../Utils"; import { ComputedField } from "../../../fields/ScriptField"; import { listSpec } from "../../../fields/Schema"; import { DocumentType } from "../../documents/DocumentTypes"; +import { Zoom, Fade, Flip, Rotate, Bounce, Roll, LightSpeed } from 'react-reveal'; +import { PresBox } from "./PresBox"; import { InkingStroke } from "../InkingStroke"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @@ -74,33 +76,84 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static getValues(doc: Doc, time: number) { const timecode = Math.round(time); return ({ + h: Cast(doc["h-indexed"], listSpec("number"), [NumCast(doc._height)]).reduce((p, h, i) => (i <= timecode && h !== undefined) || p === undefined ? h : p, undefined as any as number), + w: Cast(doc["w-indexed"], listSpec("number"), [NumCast(doc._width)]).reduce((p, w, i) => (i <= timecode && w !== undefined) || p === undefined ? w : p, undefined as any as number), x: Cast(doc["x-indexed"], listSpec("number"), [NumCast(doc.x)]).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number), y: Cast(doc["y-indexed"], listSpec("number"), [NumCast(doc.y)]).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number), + scroll: Cast(doc["scroll-indexed"], listSpec("number"), [NumCast(doc._scrollTop, 0)]).reduce((p, s, i) => (i <= timecode && s !== undefined) || p === undefined ? s : p, undefined as any as number), opacity: Cast(doc["opacity-indexed"], listSpec("number"), [NumCast(doc.opacity, 1)]).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number), }); } - public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) { + public static setValues(time: number, d: Doc, x?: number, y?: number, h?: number, w?: number, scroll?: number, opacity?: number) { const timecode = Math.round(time); + const hindexed = Cast(d["h-indexed"], listSpec("number"), []).slice(); + const windexed = Cast(d["w-indexed"], listSpec("number"), []).slice(); const xindexed = Cast(d["x-indexed"], listSpec("number"), []).slice(); const yindexed = Cast(d["y-indexed"], listSpec("number"), []).slice(); const oindexed = Cast(d["opacity-indexed"], listSpec("number"), []).slice(); + const scrollIndexed = Cast(d["scroll-indexed"], listSpec("number"), []).slice(); xindexed[timecode] = x as any as number; yindexed[timecode] = y as any as number; + hindexed[timecode] = h as any as number; + windexed[timecode] = w as any as number; oindexed[timecode] = opacity as any as number; + scrollIndexed[timecode] = scroll as any as number; d["x-indexed"] = new List<number>(xindexed); d["y-indexed"] = new List<number>(yindexed); + d["h-indexed"] = new List<number>(hindexed); + d["w-indexed"] = new List<number>(windexed); d["opacity-indexed"] = new List<number>(oindexed); + d["scroll-indexed"] = new List<number>(scrollIndexed); + if (d.appearFrame) { + if (d.appearFrame === timecode + 1) { + d["text-color"] = "red"; + } else if (d.appearFrame < timecode + 1) { + d["text-color"] = "grey"; + } else { d["text-color"] = "black"; } + } else if (d.appearFrame === 0) { + d["text-color"] = "black"; + } + } + + public static updateScrollframe(doc: Doc, time: number) { + const timecode = Math.round(time); + const scrollIndexed = Cast(doc['scroll-indexed'], listSpec("number"), null); + scrollIndexed?.length <= timecode + 1 && scrollIndexed.push(undefined as any as number); + setTimeout(() => doc.dataTransition = "inherit", 1010); + } + + public static setupScroll(doc: Doc, timecode: number, scrollProgressivize: boolean = false) { + const scrollList = new List<number>(); + scrollList[timecode] = NumCast(doc._scrollTop); + doc["scroll-indexed"] = scrollList; + doc.activeFrame = ComputedField.MakeFunction("self.currentFrame"); + doc._scrollTop = ComputedField.MakeInterpolated("scroll", "activeFrame"); } + + 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); + const hindexed = Cast(doc['h-indexed'], listSpec("number"), null); + const windexed = Cast(doc['w-indexed'], listSpec("number"), null); const opacityindexed = Cast(doc['opacity-indexed'], listSpec("number"), null); + hindexed?.length <= timecode + 1 && hindexed.push(undefined as any as number); + windexed?.length <= timecode + 1 && windexed.push(undefined as any as number); xindexed?.length <= timecode + 1 && xindexed.push(undefined as any as number); yindexed?.length <= timecode + 1 && yindexed.push(undefined as any as number); opacityindexed?.length <= timecode + 1 && opacityindexed.push(undefined as any as number); + if (doc.appearFrame) { + if (doc.appearFrame === timecode + 1) { + doc["text-color"] = "red"; + } else if (doc.appearFrame < timecode + 1) { + doc["text-color"] = "grey"; + } else { doc["text-color"] = "black"; } + } else if (doc.appearFrame === 0) { + doc["text-color"] = "black"; + } doc.dataTransition = "all 1s"; }); setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010); @@ -111,18 +164,49 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF setTimeout(() => docs.forEach(doc => doc.dataTransition = "inherit"), 1010); } + public static setupZoom(doc: Doc, zoomProgressivize: boolean = false) { + const width = new List<number>(); + const height = new List<number>(); + const top = new List<number>(); + const left = new List<number>(); + width.push(NumCast(doc.width)); + height.push(NumCast(doc.height)); + top.push(NumCast(doc.height) / -2); + left.push(NumCast(doc.width) / -2); + doc["viewfinder-width-indexed"] = width; + doc["viewfinder-height-indexed"] = height; + doc["viewfinder-top-indexed"] = top; + doc["viewfinder-left-indexed"] = left; + } + public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) { docs.forEach((doc, i) => { + if (doc.appearFrame === undefined) doc.appearFrame = 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)); + const wlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]); + const hlist = 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 < (doc.appearFrame ? doc.appearFrame : i) ? 0 : 1)); + const oarray = olist; + oarray.fill(0, 0, NumCast(doc.appearFrame) - 1); + oarray.fill(1, NumCast(doc.appearFrame), timecode); + // oarray.fill(0, 0, NumCast(doc.appearFrame) - 1); + // oarray.fill(1, NumCast(doc.appearFrame), timecode);\ + wlist[curTimecode] = NumCast(doc._width); + hlist[curTimecode] = NumCast(doc._height); xlist[curTimecode] = NumCast(doc.x); ylist[curTimecode] = NumCast(doc.y); + doc.xArray = xlist; + doc.yArray = ylist; doc["x-indexed"] = xlist; doc["y-indexed"] = ylist; - doc["opacity-indexed"] = olist; + doc["w-indexed"] = wlist; + doc["h-indexed"] = hlist; + doc["opacity-indexed"] = oarray; doc.activeFrame = ComputedField.MakeFunction("self.context?.currentFrame||0"); + doc._height = ComputedField.MakeInterpolated("h", "activeFrame"); + doc._width = ComputedField.MakeInterpolated("w", "activeFrame"); doc.x = ComputedField.MakeInterpolated("x", "activeFrame"); doc.y = ComputedField.MakeInterpolated("y", "activeFrame"); doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame"); @@ -135,6 +219,44 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF this.props.Document.y = NumCast(this.props.Document.y) + y; } + @computed get freeformNodeDiv() { + const node = <DocumentView {...this.props} + nudge={this.nudge} + dragDivName={"collectionFreeFormDocumentView-container"} + ContentScaling={this.contentScaling} + ScreenToLocalTransform={this.getTransform} + backgroundColor={this.props.backgroundColor} + opacity={this.opacity} + NativeHeight={this.NativeHeight} + NativeWidth={this.NativeWidth} + PanelWidth={this.panelWidth} + PanelHeight={this.panelHeight} />; + if (PresBox.Instance && this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc) { + const effectProps = { + left: this.layoutDoc.presEffectDirection === 'left', + right: this.layoutDoc.presEffectDirection === 'right', + top: this.layoutDoc.presEffectDirection === 'top', + bottom: this.layoutDoc.presEffectDirection === 'bottom', + opposite: true, + delay: this.layoutDoc.presTransition, + // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc, + }; + switch (this.layoutDoc.presEffect) { + case "Zoom": return (<Zoom {...effectProps}>{node}</Zoom>); break; + case "Fade": return (<Fade {...effectProps}>{node}</Fade>); break; + case "Flip": return (<Flip {...effectProps}>{node}</Flip>); break; + case "Rotate": return (<Rotate {...effectProps}>{node}</Rotate>); break; + case "Bounce": return (<Bounce {...effectProps}>{node}</Bounce>); break; + case "Roll": return (<Roll {...effectProps}>{node}</Roll>); break; + case "LightSpeed": return (<LightSpeed {...effectProps}>{node}</LightSpeed>); break; + case "None": return node; break; + default: return node; break; + } + } else { + return node; + } + } + contentScaling = () => this.nativeWidth > 0 && !this.props.fitToBox && !this.freezeDimensions ? this.width / this.nativeWidth : 1; panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.()); panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.()); @@ -165,6 +287,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF display: this.ZInd === -99 ? "none" : undefined, pointerEvents: this.props.Document.isBackground || this.Opacity === 0 || this.props.Document.type === DocumentType.INK || this.props.Document.isInkMask ? "none" : this.props.pointerEvents ? "all" : undefined }} > + {Doc.UserDoc().renderStyle !== "comic" ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }}> <svg style={{ transform: `scale(1,${this.props.PanelHeight() / this.props.PanelWidth()})`, transformOrigin: "top left", overflow: "visible" }} viewBox="0 0 12 14"> @@ -174,17 +297,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF </div>} {!this.props.fitToBox ? - <DocumentView {...this.props} - nudge={this.nudge} - dragDivName={"collectionFreeFormDocumentView-container"} - ContentScaling={this.contentScaling} - ScreenToLocalTransform={this.getTransform} - backgroundColor={this.props.backgroundColor} - opacity={this.opacity} - NativeHeight={this.NativeHeight} - NativeWidth={this.NativeWidth} - PanelWidth={this.panelWidth} - PanelHeight={this.panelHeight} /> + <>{this.freeformNodeDiv}</> : <ContentFittingDocumentView {...this.props} ContainingCollectionDoc={this.props.ContainingCollectionDoc} DataDoc={this.props.DataDoc} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 1282bbee5..2408b3906 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -18,6 +18,7 @@ import { DocHolderBox } from "./DocHolderBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FontIconBox } from "./FontIconBox"; +import { MenuIconBox } from "./MenuIconBox"; import { FieldView, FieldViewProps } from "./FieldView"; import { FormattedTextBox } from "./formattedText/FormattedTextBox"; import { ImageBox } from "./ImageBox"; @@ -189,7 +190,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { blacklistedAttrs={[]} renderInWrapper={false} components={{ - FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView, + FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, MenuIconBox, LabelBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 38670817b..b9e685b44 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -998,13 +998,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document); const borderRounding = this.layoutDoc.borderRounding; const localScale = fullDegree; - const highlightColors = Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? ["transparent", "#65350c", "#65350c", "yellow", "magenta", "cyan", "orange"] : ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"]; let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear && this.props.Document.type !== DocumentType.INK; - highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way + highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way const topmost = this.topMost ? "-topmost" : ""; return <div className={`documentView-node${topmost}`} id={this.props.Document[Id]} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 34b07f351..e631ad5fe 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -45,6 +45,7 @@ export interface FieldViewProps { whenActiveChanged: (isActive: boolean) => void; dontRegisterView?: boolean; focus: (doc: Doc) => void; + presMultiSelect?: (doc: Doc) => void; //added for selecting multiple documents in a presentation ignoreAutoHeight?: boolean; PanelWidth: () => number; PanelHeight: () => number; diff --git a/src/client/views/nodes/MenuIconBox.scss b/src/client/views/nodes/MenuIconBox.scss new file mode 100644 index 000000000..1b72f5a8f --- /dev/null +++ b/src/client/views/nodes/MenuIconBox.scss @@ -0,0 +1,49 @@ +.menuButton { + //padding: 7px; + padding-left: 7px; + width: 100%; + width: 60px; + height: 70px; + + .menuButton-wrap { + width: 45px; + /* padding: 5px; */ + touch-action: none; + background: black; + transform-origin: top left; + /* margin-bottom: 5px; */ + margin-top: 5px; + margin-right: 25px; + border-radius: 8px; + + &:hover { + background: rgb(61, 61, 61); + cursor: pointer; + } + } + + .menuButton-label { + color: white; + margin-right: 4px; + border-radius: 8px; + width: 42px; + position: relative; + text-align: center; + font-size: 8px; + margin-top: 1px; + letter-spacing: normal; + padding: 3px; + background-color: inherit; + } + + .menuButton-icon { + width: auto; + height: 35px; + padding: 5px; + } + + svg { + width: 95% !important; + height: 95%; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/MenuIconBox.tsx b/src/client/views/nodes/MenuIconBox.tsx new file mode 100644 index 000000000..0aa7b327e --- /dev/null +++ b/src/client/views/nodes/MenuIconBox.tsx @@ -0,0 +1,33 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { createSchema, makeInterface } from '../../../fields/Schema'; +import { StrCast } from '../../../fields/Types'; +import { DocComponent } from '../DocComponent'; +import { FieldView, FieldViewProps } from './FieldView'; +import './MenuIconBox.scss'; +const MenuIconSchema = createSchema({ + icon: "string" +}); + +type MenuIconDocument = makeInterface<[typeof MenuIconSchema]>; +const MenuIconDocument = makeInterface(MenuIconSchema); +@observer +export class MenuIconBox extends DocComponent<FieldViewProps, MenuIconDocument>(MenuIconDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MenuIconBox, fieldKey); } + _ref: React.RefObject<HTMLButtonElement> = React.createRef(); + + render() { + + const color = this.props.backgroundColor?.(this.props.Document) === "lightgrey" ? "black" : "white"; + const menuBTN = <div className="menuButton" style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document) }}> + <div className="menuButton-wrap" + style={{ backgroundColor: this.props.backgroundColor?.(this.props.Document) }} > + <FontAwesomeIcon className="menuButton-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={color} size="lg" /> + <div className="menuButton-label" style={{ color: color }}> {this.dataDoc.title} </div> + </div> + </div>; + + return menuBTN; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index 9f6af1bde..16821f5bc 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -1,7 +1,9 @@ .presBox-cont { position: absolute; + display: block; pointer-events: inherit; z-index: 2; + font-family: Roboto; box-shadow: #AAAAAA .2vw .2vw .4vw; width: 100%; min-width: 20px; @@ -12,75 +14,865 @@ transition: 0.7s opacity ease; .presBox-listCont { - position: absolute; + position: relative; height: calc(100% - 25px); width: 100%; + margin-top: 3px; + } + + .presBox-toolbar { + display: none; + } + + .presBox-toolbar-dropdown { + border-radius: 5px; + background-color: white; + transform: translate(8px, -5px); + box-shadow: 1px 1px 4px 4px rgba(0, 0, 0, 0.25); + z-index: 1000; + width: 315; + height: max-content; + justify-self: center; + letter-spacing: normal; + height: max-content; + font-weight: 500; + position: relative; + font-size: 13; } - .presBox-buttons { + .presBox-toolbar.active { + position: relative; + display: inline-flex; + align-items: center; + height: 30px; width: 100%; - background: gray; - padding-top: 5px; - padding-bottom: 5px; + color: white; + background-color: #323232; + + .toolbar-button { + margin-left: 10px; + margin-right: 10px; + letter-spacing: 0; + display: flex; + align-items: center; + transition: 0.5s; + } + + .toolbar-button.active { + color: #AEDDF8; + } + + .toolbar-transitionButtons { + display: block; + + .toolbar-transition { + display: flex; + font-size: 10; + width: 100; + background-color: rgba(0, 0, 0, 0); + min-width: max-content; + + .toolbar-icon { + margin-right: 5px; + } + } + } + } + + .toolbar-moreInfo { + position: absolute; + right: 5px; + display: flex; + width: max-content; + height: 25px; + /* background-color: pink; */ + justify-content: center; + transform: rotate(90deg); + align-items: center; + transition: 0.7s ease; + + .toolbar-moreInfoBall { + width: 4px; + height: 4px; + border-radius: 100%; + background-color: white; + margin: 1px; + position: relative; + } + } + + .toolbar-moreInfo.active { + transform: rotate(0deg); + } + + .toolbar-divider { + border-left: solid #ffffff70 0.5px; + height: 20px; + } +} + +.dropdown { + font-size: 10; + margin-left: 5px; + color: darkgrey; + transition: 0.5s ease; +} + +.dropdown.active { + transform: rotate(180deg); + color: #AEDDF8; + opacity: 0.8; +} + +.presBox-ribbon { + position: relative; + display: inline; + font-family: Roboto; + /* background-color: white; */ + color: black; + width: 100%; + height: max-content; + z-index: 100; + transition: 0.7s; + + .ribbon-doubleButton { + display: inline-flex; + } + + .presBox-reactiveGrid { display: grid; - grid-column-end: 4; - grid-column-start: 1; + justify-content: flex-start; + align-items: center; + grid-template-columns: repeat(auto-fit, 70px); + } - .presBox-viewPicker { - height: 25; + .ribbon-property { + font-size: 11; + font-weight: 200; + height: 20; + background-color: #ececec; + color: black; + border: solid 1px black; + display: flex; + margin-left: 5px; + margin-top: 5px; + margin-bottom: 5px; + /* border-radius: 5px; */ + margin-right: 5px; + width: max-content; + justify-content: center; + align-items: center; + padding-right: 10px; + padding-left: 10px; + } + + // .toolbar-slider { + // position: relative; + // align-self: center; + // justify-self: left; + // -webkit-appearance: none; + // background-color: #5b9ddd; + // overflow: hidden; + // margin-top: 5px; + // width: 100%; + // max-width: 200px; + + // } + + // .toolbar-slider:focus { + // outline: none; + // } + + // .effectDirection { + // justify-self: center; + // align-self: center; + // align-items: center; + // justify-content: center; + // grid-template-columns: 13px 13px 13px; + // display: grid; + // } + + // .toolbar-slider::-webkit-slider-thumb { + // -webkit-appearance: none; + // background-color: #5b9ddd; + // box-shadow: -100 vw 0 0 100vw pink; + // width: 12px; + // height: 12px; + // } + + // .toolbar-slider::-webkit-slider-runnable-track { + // position: relative; + // align-self: center; + // justify-self: left; + // -webkit-appearance: none; + // background-color: #5b9ddd; + // margin-top: 5px; + // border: solid 1px black; + // border-radius: 10px; + // height: 5px; + // left: 0px; + // } + .presBox-subheading { + font-size: 11; + font-weight: 400; + margin-top: 10px; + } + + @media screen and (-webkit-min-device-pixel-ratio:0) { + .toolbar-slider { + margin-top: 5px; position: relative; - display: inline-block; - grid-column: 1/2; - min-width: 15px; + align-self: center; + justify-self: left; + overflow: hidden; + width: 100%; + height: 10px; + border-radius: 10px; + -webkit-appearance: none; + background-color: #ececec; } - select { - background: #323232; + .toolbar-slider:focus { + outline: none; + } + + .toolbar-slider::-webkit-slider-runnable-track { + height: 10px; + -webkit-appearance: none; + margin-top: -1px; + } + + .toolbar-slider::-webkit-slider-thumb { + width: 10px; + -webkit-appearance: none; + height: 10px; + cursor: ew-resize; + background: #5b9ddd; + box-shadow: -100vw 0 0 100vw #aedef8; + } + } + + .slider-headers { + position: relative; + display: grid; + justify-content: space-between; + width: 100%; + height: max-content; + grid-template-columns: auto auto auto; + grid-template-rows: max-content; + font-weight: 100; + margin-top: 3px; + font-size: 10px; + } + + .slider-value { + top: -20; + color: #2f86a2; + position: absolute; + } + + .slider-value.none, + .slider-headers.none, + .toolbar-slider.none { + display: none; + } + + .dropdown-header { + padding-bottom: 10px; + font-weight: 800; + text-align: center; + font-size: 16; + width: 90%; + color: black; + transform: translate(5%, 0px); + border-bottom: solid 2px darkgrey; + } + + + .ribbon-textInput { + border-radius: 2px; + height: 20px; + font-size: 11.5; + font-weight: 100; + align-self: center; + justify-self: left; + margin-top: 5px; + padding-left: 10px; + background-color: rgb(222, 222, 222); + border: solid 1px black; + min-width: 80px; + max-width: 200px; + width: 100%; + } + + .ribbon-frameSelector { + border: black solid 1px; + width: 60px; + height: 20px; + margin-top: 5px; + display: grid; + grid-template-columns: auto 27px auto; + position: relative; + border-radius: 5px; + overflow: hidden; + align-items: center; + justify-self: left; + + .fwdKeyframe, + .backKeyframe { + cursor: pointer; + position: relative; + height: 100%; + background: #d5dce2; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + color: black; + } + + .numKeyframe { + font-size: 10; + font-weight: 600; + position: relative; + color: black; + display: flex; + width: 100%; + height: 100%; + text-align: center; + align-items: center; + justify-content: center; + } + } + + .ribbon-final-box { + align-self: flex-start; + justify-self: center; + display: grid; + margin-top: 10px; + grid-template-rows: auto auto; + /* padding-left: 10px; */ + /* padding-right: 10px; */ + letter-spacing: normal; + min-width: max-content; + width: 100%; + font-size: 13; + font-weight: 500; + position: relative; + + + .ribbon-final-button { + position: relative; + font-size: 11; + font-weight: normal; + letter-spacing: normal; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 5px; + height: 25px; color: white; + width: 100%; + max-width: 120; + padding-left: 10; + padding-right: 10; + border-radius: 10px; + background-color: #979797; } - .presBox-button { - margin-right: 2.5%; - margin-left: 2.5%; + .ribbon-final-button-hidden { + position: relative; + font-size: 11; + font-weight: normal; + letter-spacing: normal; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 5px; height: 25px; + color: lightgrey; + width: 100%; + max-width: 120; + padding-left: 10; + padding-right: 10; + border-radius: 10px; + background-color: black; + } + } + + .selectedList { + display: block; + min-width: 50; + max-width: 120; + height: 70; + overflow-y: scroll; + + .selectedList-items { + font-size: 7; + font-weight: normal; + } + } + + .ribbon-button { + font-size: 10.5; + font-weight: 200; + height: 20; + background-color: #dedede; + border: solid 1px black; + display: flex; + margin-top: 5px; + margin-bottom: 5px; + border-radius: 5px; + margin-right: 5px; + width: max-content; + justify-content: center; + align-items: center; + padding-right: 10px; + padding-left: 10px; + } + + .ribbon-button.active { + background-color: #aedef8; + } + + .ribbon-button:hover { + background-color: lightgrey; + } + + svg.svg-inline--fa.fa-thumbtack.fa-w-12.toolbar-thumbtack { + right: 40; + position: absolute; + transform: rotate(45deg); + } + + .ribbon-box { + display: grid; + grid-template-rows: max-content auto; + justify-self: center; + margin-top: 10px; + /* padding-left: 10px; */ + padding-right: 10px; + letter-spacing: normal; + width: 100%; + /* max-width: 100%; */ + height: max-content; + font-weight: 500; + position: relative; + font-size: 13; + padding-bottom: 10px; + border-bottom: solid 1px darkgrey; + + .presBox-dropdown:hover { + border: solid 1px #378AD8; + border-bottom-left-radius: 0px; + + .presBox-dropdownOption { + font-size: 11; + display: block; + padding-left: 10px; + padding-right: 5px; + padding-top: 3; + padding-bottom: 3; + } + + .presBox-dropdownOption:hover { + position: relative; + background-color: lightgrey; + } + + .presBox-dropdownOption.active { + position: relative; + background-color: #aedef8; + } + + .presBox-dropdownOptions { + position: absolute; + top: 29px; + left: -1px; + z-index: 200; + width: 85%; + min-width: max-content; + display: block; + background: #FFFFFF; + border: 0.5px solid #979797; + box-sizing: border-box; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + } + + .presBox-dropdownIcon { + color: #378AD8; + } + } + + .presBox-dropdown { + display: grid; + grid-template-columns: auto 20%; + position: relative; + border: solid 1px black; + background-color: #dedede; border-radius: 5px; - display: flex; + font-size: 10; + height: 30; + padding-left: 5px; align-items: center; - background: #323232; - color: white; + margin-top: 5px; + margin-bottom: 5px; + font-weight: 200; + width: 100%; + min-width: max-content; + max-width: 200px; + overflow: visible; + + .presBox-dropdownOptions { + display: none; + } - svg { - margin: auto; + .presBox-dropdownIcon { + position: relative; + color: black; + align-self: center; + justify-self: center; + margin-right: 2px; } } + } +} + +.presBox-ribbon.active { + display: grid; + grid-template-columns: auto auto auto auto auto; + grid-template-rows: 100%; + height: 100px; + padding-top: 5px; + padding-bottom: 5px; + border: solid 1px black; + // overflow: auto; + + ::-webkit-scrollbar { + -webkit-appearance: none; + height: 3px; + width: 8px; + } + + ::-webkit-scrollbar-thumb { + border-radius: 2px; + } +} - .collectionViewBaseChrome-viewPicker { - min-width: 50; - width: 5%; - height: 25; +.dropdown-play-button { + font-size: 12; + padding-left: 5px; + padding-right: 5px; + padding-top: 5px; + padding-bottom: 5px; + text-align: left; + justify-content: left; +} + +.dropdown-play-button:hover { + background-color: lightgrey; +} + +.presBox-button-left { + position: relative; + align-self: flex-start; + justify-self: flex-start; + width: 80%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} + +.presBox-button-right { + position: relative; + text-align: center; + border-left: solid 1px darkgrey; + width: 20%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + +.presBox-button-right.active { + background-color: #223063; + border: #aedcf6 solid 1px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.8); +} + +.dropdown-play { + right: 0px; + top: calc(100% + 2px); + display: none; + border-radius: 5px; + width: max-content; + min-height: 20px; + height: max-content; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.8); + z-index: 200; + background-color: white; + color: black; + position: absolute; + overflow: hidden; +} + +.dropdown-play.active { + display: block; +} + +.open-layout { + position: relative; + display: flex; + align-items: center; + justify-content: center; + transform: translate(0px, -1px); + background-color: #dedede; + width: 40px; + height: 15px; + align-self: center; + justify-self: center; + border: solid 1px black; + border-top: 0px; + border-bottom-right-radius: 7px; + border-bottom-left-radius: 7px; +} + +.layout-container { + padding: 5px; + display: grid; + background-color: #dedede; + grid-template-columns: repeat(auto-fit, minmax(90px, 100px)); + width: 100%; + border: solid 1px black; + min-width: 100px; + overflow: hidden; + + .layout:hover { + border: solid 2px #5c9edd; + } + + .layout { + align-self: center; + justify-self: center; + margin-top: 5; + margin-bottom: 5; + position: relative; + height: 55px; + min-width: 90px; + width: 90px; + overflow: hidden; + background-color: white; + border: solid darkgrey 1px; + display: grid; + grid-template-rows: auto; + align-items: center; + text-align: center; + + .title { position: relative; - display: inline-block; + align-self: end; + padding-left: 3px; + margin-left: 3px; + margin-right: 3px; + height: 13; + font-size: 12; + display: flex; + background-color: #f1efec; } + + .subtitle { + align-self: flex-start; + position: relative; + padding-left: 3px; + margin-left: 3px; + margin-right: 3px; + font-weight: 400; + height: 13; + font-size: 9; + display: flex; + background-color: #f1efec; + } + + .content { + position: relative; + font-weight: 200; + align-self: flex-start; + padding-left: 3px; + margin-left: 3px; + margin-right: 3px; + height: 13; + font-size: 10; + display: flex; + background-color: #f1efec; + height: 33; + text-align: left; + font-size: 8px; + } + } +} + +.presBox-buttons { + position: relative; + width: 100%; + background: gray; + padding-top: 5px; + padding-bottom: 5px; + display: grid; + grid-template-columns: auto auto; + + .presBox-viewPicker { + height: 25; + position: relative; + display: inline-block; + grid-column: 1; + border-radius: 5px; + min-width: 15px; + max-width: 100px; + left: 8px; + } + + .presBox-presentPanel { + display: flex; + justify-self: end; + width: 100%; + max-width: 300px; + min-width: 150px; + } + + + + select { + background: #323232; + color: white; } - .presBox-backward, - .presBox-forward { - width: 25px; + .presBox-button { + height: 25px; border-radius: 5px; - top: 50%; + display: none; + justify-content: center; + align-content: center; + align-items: center; + text-align: center; + letter-spacing: normal; + width: inherit; + background: #323232; + color: white; + } + + .presBox-button.active { + display: flex; + } + + .presBox-button.active:hover { + background-color: #233163; + } + + .presBox-button.edit { + display: flex; + max-width: 25px; + } + + .presBox-button.present { + display: flex; + min-width: 100px; + width: 100px; position: absolute; - display: inline-block; + right: 10px; + + .present-icon { + margin-right: 7px; + } } - .presBox-backward { - left: 5; + + .miniPresOverlay { + background-color: #323232; + color: white; + border-radius: 5px; + grid-template-rows: 100%; + height: 25; + width: 200; + justify-content: space-evenly; + align-items: center; + display: flex; + position: absolute; + right: 10px; + + .miniPres-button-text { + display: flex; + height: 20; + width: max-content; + font-family: Roboto; + font-weight: 400; + letter-spacing: normal; + border-radius: 5px; + align-items: center; + justify-content: center; + transition: all 0.3s; + } + + .miniPres-divider { + width: 0.5px; + height: 80%; + border-right: solid 1px #5a5a5a; + } + + .miniPres-button { + display: flex; + height: 20; + min-width: 20; + border-radius: 100%; + align-items: center; + justify-content: center; + transition: all 0.3s; + } + + .miniPres-button:hover { + background-color: #5a5a5a; + } + + .miniPres-button-text:hover { + background-color: #5a5a5a; + } } - .presBox-forward { - right: 5; + + + .collectionViewBaseChrome-viewPicker { + min-width: 50; + width: 5%; + height: 25; + position: relative; + display: inline-block; + left: 8px; } } +.presBox-backward, +.presBox-forward { + width: 25px; + border-radius: 5px; + top: 50%; + position: absolute; + display: inline-block; +} + +.presBox-backward { + left: 5; +} + +.presBox-forward { + right: 5; +} + // CSS adjusted for mobile devices @media only screen and (max-device-width: 480px) { .presBox-cont .presBox-buttons { diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index a304ced18..ccf1fb8e3 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -1,25 +1,39 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable } from "mobx"; +import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DocCastAsync } from "../../../fields/Doc"; +import { Doc, DocListCast, DocCastAsync, WidthSym } from "../../../fields/Doc"; import { InkTool } from "../../../fields/InkField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; -import { returnFalse, returnOne } from "../../../Utils"; +import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types"; +import { returnFalse, returnOne, numberRange, returnTrue } from "../../../Utils"; import { documentSchema } from "../../../fields/documentSchemas"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionDockingView, DockedFrameRenderer } from "../collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from "../collections/CollectionView"; import { FieldView, FieldViewProps } from './FieldView'; +import { DocumentType } from "../../documents/DocumentTypes"; import "./PresBox.scss"; import { ViewBoxBaseComponent } from "../DocComponent"; -import { makeInterface } from "../../../fields/Schema"; -import { Docs } from "../../documents/Documents"; +import { makeInterface, listSpec } from "../../../fields/Schema"; +import { Docs, DocUtils } from "../../documents/Documents"; import { PrefetchProxy } from "../../../fields/Proxy"; import { ScriptField } from "../../../fields/ScriptField"; import { Scripting } from "../../util/Scripting"; import { InkingStroke } from "../InkingStroke"; +import { HighlightSpanKind } from "typescript"; +import { SearchUtil } from "../../util/SearchUtil"; +import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; +import { child } from "serializr"; +import { List } from "../../../fields/List"; +import { Tooltip } from "@material-ui/core"; +import { CollectionFreeFormViewChrome } from "../collections/CollectionMenu"; +import { conformsTo } from "lodash"; +import { translate } from "googleapis/build/src/apis/translate"; +import { DragManager, dropActionType } from "../../util/DragManager"; +import { actionAsync } from "mobx-utils"; +import { SelectionManager } from "../../util/SelectionManager"; +import { AudioBox } from "./AudioBox"; type PresBoxSchema = makeInterface<[typeof documentSchema]>; const PresBoxDocument = makeInterface(documentSchema); @@ -27,56 +41,99 @@ const PresBoxDocument = makeInterface(documentSchema); @observer export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + private treedropDisposer?: DragManager.DragDropDisposer; + static Instance: PresBox; @observable _isChildActive = false; @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } constructor(props: any) { super(props); + PresBox.Instance = this; if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data" + title: "pres element template", backgroundColor: "transparent", _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" })); // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent // the preselement docs from being part of multiple presentations since they would all have the same field, or we'd have to keep per-presentation data - // stored on each pres element. + // stored on each pres element. (this.presElement as Doc).lookupField = ScriptField.MakeFunction("lookupPresBoxField(container, field, data)", { field: "string", data: Doc.name, container: Doc.name }); } this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox } + + componentDidMount() { this.rootDoc.presBox = this.rootDoc; this.rootDoc._forceRenderEngine = "timeline"; this.rootDoc._replacedChrome = "replaced"; + this.layoutDoc.presStatus = "edit"; + this.layoutDoc._gridGap = 5; + // document.addEventListener("keydown", this.keyEvents, false); + } + + componentWillUnmount() { + this.treedropDisposer?.(); + } + + onPointerOver = () => { + document.addEventListener("keydown", this.keyEvents, true); } - updateCurrentPresentation = () => Doc.UserDoc().activePresentation = this.rootDoc; + + onPointerLeave = () => { + document.removeEventListener("keydown", this.keyEvents, true); + } + + updateCurrentPresentation = () => { + Doc.UserDoc().activePresentation = this.rootDoc; + } + + @observable _moveOnFromAudio: boolean = false; @undoBatch @action next = () => { this.updateCurrentPresentation(); + const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null); const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null); + const childDocs = DocListCast(presTargetDoc[Doc.LayoutFieldKey(presTargetDoc)]); + const currentFrame = Cast(presTargetDoc.currentFrame, "number", null); const lastFrame = Cast(presTargetDoc.lastFrame, "number", null); const curFrame = NumCast(presTargetDoc.currentFrame); - if (lastFrame !== undefined && curFrame < lastFrame) { + let internalFrames: boolean = false; + if (presTargetDoc.presProgressivize || presTargetDoc.zoomProgressivize || presTargetDoc.scrollProgressivize) internalFrames = true; + // Case 1: There are still other frames and should go through all frames before going to next slide + if (internalFrames && lastFrame !== undefined && curFrame < lastFrame) { presTargetDoc._viewTransition = "all 1s"; setTimeout(() => presTargetDoc._viewTransition = undefined, 1010); presTargetDoc.currentFrame = curFrame + 1; - } - else if (this.childDocs[this.itemIndex + 1] !== undefined) { - let nextSelected = this.itemIndex + 1; - this.gotoDocument(nextSelected, this.itemIndex); - - for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) { - if (!this.childDocs[nextSelected].groupButton) { - break; - } else { - this.gotoDocument(nextSelected, this.itemIndex); - } + if (presTargetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(presTargetDoc, currentFrame); + if (presTargetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); + if (presTargetDoc.zoomProgressivize) this.zoomProgressivizeNext(presTargetDoc); + // Case 2: Audio or video therefore wait to play the audio or video before moving on + } else if ((presTargetDoc.type === DocumentType.VID || presTargetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio) { + if (presTargetDoc.type === DocumentType.AUDIO) { + AudioBox.Instance.playFrom(0); + this._moveOnFromAudio = true; + } + if (presTargetDoc.type === DocumentType.VID) { + this._moveOnFromAudio = true; } + // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide + } else if (activeNext !== undefined) { + if (!presTargetDoc.presProgressivize) { + const nextTagDoc = Cast(this.childDocs[this.itemIndex + 1].presentationTargetDoc, Doc, null); + const nextChildDocs = DocListCast(nextTagDoc[Doc.LayoutFieldKey(presTargetDoc)]); + nextChildDocs.forEach((doc, i) => { + doc.opacity = 1; + }); + } + const nextSelected = this.itemIndex + 1; + this.gotoDocument(nextSelected, this.itemIndex); + this._moveOnFromAudio = false; } } @@ -86,59 +143,72 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.updateCurrentPresentation(); const docAtCurrent = this.childDocs[this.itemIndex]; if (docAtCurrent) { - //check if any of the group members had used zooming in including the current document - //If so making sure to zoom out, which goes back to state before zooming action let prevSelected = this.itemIndex; - let didZoom = docAtCurrent.zoomButton; - for (; !didZoom && prevSelected > 0 && this.childDocs[prevSelected].groupButton; prevSelected--) { - didZoom = this.childDocs[prevSelected].zoomButton; - } prevSelected = Math.max(0, prevSelected - 1); - this.gotoDocument(prevSelected, this.itemIndex); } } - /** - * This is the method that checks for the actions that need to be performed - * after the document has been presented, which involves 3 button options: - * Hide Until Presented, Hide After Presented, Fade After Presented - */ - showAfterPresented = (index: number) => { - this.updateCurrentPresentation(); - this.childDocs.forEach((doc, ind) => { - const presTargetDoc = doc.presentationTargetDoc as Doc; - //the order of cases is aligned based on priority - if (doc.presHideTillShownButton && ind <= index) { - presTargetDoc.opacity = 1; - } - if (doc.presHideAfterButton && ind < index) { - presTargetDoc.opacity = 0; - } - if (doc.presFadeButton && ind < index) { - presTargetDoc.opacity = 0.5; + zoomProgressivizeNext = (presTargetDoc: Doc) => { + const srcContext = Cast(presTargetDoc.context, Doc, null); + const docView = DocumentManager.Instance.getDocumentView(presTargetDoc); + const vfLeft: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-left-indexed"]); + const vfWidth: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-width-indexed"]); + const vfTop: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-top-indexed"]); + const vfHeight: number = this.checkList(presTargetDoc, presTargetDoc["viewfinder-height-indexed"]); + // Case 1: document that is not a Golden Layout tab + if (srcContext) { + const srcDocView = DocumentManager.Instance.getDocumentView(srcContext); + if (srcDocView) { + const layoutdoc = Doc.Layout(presTargetDoc); + const panelWidth: number = srcDocView.props.PanelWidth(); + const panelHeight: number = srcDocView.props.PanelHeight(); + const newPanX = NumCast(presTargetDoc.x) + NumCast(layoutdoc._width) / 2; + const newPanY = NumCast(presTargetDoc.y) + NumCast(layoutdoc._height) / 2; + const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); + srcContext._panX = newPanX + (vfLeft + (vfWidth / 2)); + srcContext._panY = newPanY + (vfTop + (vfHeight / 2)); + srcContext._viewScale = newScale; } - }); + } + // Case 2: document is the containing collection + if (docView && !srcContext) { + const panelWidth: number = docView.props.PanelWidth(); + const panelHeight: number = docView.props.PanelHeight(); + const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); + presTargetDoc._panX = vfLeft + (vfWidth / 2); + presTargetDoc._panY = vfTop + (vfWidth / 2); + presTargetDoc._viewScale = newScale; + } + const resize = document.getElementById('resizable'); + if (resize) { + resize.style.width = vfWidth + 'px'; + resize.style.height = vfHeight + 'px'; + resize.style.top = vfTop + 'px'; + resize.style.left = vfLeft + 'px'; + } } - /** - * This is the method that checks for the actions that need to be performed - * before the document has been presented, which involves 3 button options: - * Hide Until Presented, Hide After Presented, Fade After Presented - */ - hideIfNotPresented = (index: number) => { - this.updateCurrentPresentation(); - this.childDocs.forEach((key, ind) => { - //the order of cases is aligned based on priority - const presTargetDoc = key.presentationTargetDoc as Doc; - if (key.hideAfterButton && ind >= index) { - presTargetDoc.opacity = 1; - } - if (key.fadeButton && ind >= index) { - presTargetDoc.opacity = 1; + + @action + onHideDocument = () => { + this.childDocs.forEach((doc, index) => { + const curDoc = Cast(doc, Doc, null); + const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); + if (tagDoc) tagDoc.opacity = 1; + if (curDoc.presHideTillShownButton) { + if (index > this.itemIndex) { + tagDoc.opacity = 0; + } else if (!curDoc.presHideAfterButton) { + tagDoc.opacity = 1; + } } - if (key.hideTillShownButton && ind > index) { - presTargetDoc.opacity = 0; + if (curDoc.presHideAfterButton) { + if (index < this.itemIndex) { + tagDoc.opacity = 0; + } else if (!curDoc.presHideTillShownButton) { + tagDoc.opacity = 1; + } } }); } @@ -146,50 +216,63 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> /** * This method makes sure that cursor navigates to the element that * has the option open and last in the group. If not in the group, and it has - * te option open, navigates to that element. + * the option open, navigates to that element. */ - navigateToElement = async (curDoc: Doc, fromDocIndex: number) => { - this.updateCurrentPresentation(); - let docToJump = curDoc; - let willZoom = false; - - const presDocs = DocListCast(this.dataDoc[this.props.fieldKey]); - let nextSelected = presDocs.indexOf(curDoc); - const currentDocGroups: Doc[] = []; - for (; nextSelected < presDocs.length - 1; nextSelected++) { - if (!presDocs[nextSelected + 1].groupButton) { - break; - } - currentDocGroups.push(presDocs[nextSelected]); - } + navigateToElement = async (curDoc: Doc) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const srcContext = await DocCastAsync(targetDoc.context); + const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); + const collectionDocView = presCollection ? await DocumentManager.Instance.getDocumentView(presCollection) : undefined; + this.turnOffEdit(); - currentDocGroups.forEach((doc: Doc, index: number) => { - if (doc.presNavButton) { - docToJump = doc; - willZoom = false; - } - if (doc.presZoomButton) { - docToJump = doc; - willZoom = true; + if (this.itemIndex >= 0) { + if (targetDoc) { + if (srcContext) this.layoutDoc.presCollection = srcContext; + } else if (targetDoc) this.layoutDoc.presCollection = targetDoc; + } + if (collectionDocView) { + if (srcContext && srcContext !== presCollection) { + // Case 1: new srcContext inside of current collection so add a new tab to the current pres collection + collectionDocView.props.addDocTab(srcContext, "inPlace"); } - }); + } + // else if (srcContext) { + // console.log("Case 2: srcContext - not open and collection containing this document exists, so open collection that contains it and then await zooming in on document"); + // this.props.addDocTab(srcContext, "onRight"); + // } else if (!srcContext) { + // console.log("Case 3: !srcContext - no collection containing this document, therefore open document itself on right"); + // this.props.addDocTab(targetDoc, "onRight"); + // } + this.updateCurrentPresentation(); + const docToJump = curDoc; + const willZoom = false; //docToJump stayed same meaning, it was not in the group or was the last element in the group - const aliasOf = await DocCastAsync(docToJump.aliasOf); - const srcContext = aliasOf && await DocCastAsync(aliasOf.context); - if (docToJump === curDoc) { + if (targetDoc.zoomProgressivize && this.rootDoc.presStatus !== 'edit') { + this.zoomProgressivizeNext(targetDoc); + } else if (docToJump === curDoc) { //checking if curDoc has navigation open - const target = (await DocCastAsync(curDoc.presentationTargetDoc)) || curDoc; - if (curDoc.presNavButton && target) { - DocumentManager.Instance.jumpToDocument(target, false, undefined, srcContext); - } else if (curDoc.presZoomButton && target) { + if (curDoc.presNavButton && targetDoc) { + await DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, srcContext); + } else if (curDoc.presZoomButton && targetDoc) { //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(target, true, undefined, srcContext); + await DocumentManager.Instance.jumpToDocument(targetDoc, true, undefined, srcContext); } } else { //awaiting jump so that new scale can be found, since jumping is async - const presTargetDoc = await DocCastAsync(docToJump.presentationTargetDoc); - presTargetDoc && await DocumentManager.Instance.jumpToDocument(presTargetDoc, willZoom, undefined, srcContext); + targetDoc && await DocumentManager.Instance.jumpToDocument(targetDoc, willZoom, undefined, srcContext); + } + if (activeItem.presPinView) { + targetDoc._panX = activeItem.presPinViewX; + targetDoc._panY = activeItem.presPinViewY; + targetDoc._viewScale = activeItem.presPinViewScale; + } + if (collectionDocView && activeItem.openDocument) { + collectionDocView.props.addDocTab(activeItem, "inPlace"); + } + if (targetDoc.presWebsiteData) { + targetDoc.data = targetDoc.presWebsiteData; } } @@ -201,30 +284,43 @@ 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.lastFrame !== undefined) { + if (presTargetDoc?.lastFrame !== undefined) { presTargetDoc.currentFrame = 0; } - - if (!this.layoutDoc.presStatus) { - this.layoutDoc.presStatus = true; - this.startPresentation(index); - } - - this.navigateToElement(this.childDocs[index], fromDoc); - this.hideIfNotPresented(index); - this.showAfterPresented(index); + this.navigateToElement(this.childDocs[index]); + this._selectedArray = [this.childDocs[index]]; + // this.hideIfNotPresented(index); + // this.showAfterPresented(index); + // this.hideDocumentInPres(); + this.onHideDocument(); + // this.onHideAfterPresClick(); } }); + + @observable _presTimer!: NodeJS.Timeout; + //The function that starts or resets presentaton functionally, depending on status flag. - startOrResetPres = () => { + @undoBatch + @action + startOrResetPres = (startSlide: number) => { this.updateCurrentPresentation(); - if (this.layoutDoc.presStatus) { - this.resetPresentation(); + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + if (this.layoutDoc.presStatus === "auto") { + if (this._presTimer) clearInterval(this._presTimer); + this.layoutDoc.presStatus = "manual"; } else { - this.layoutDoc.presStatus = true; - this.startPresentation(0); - this.gotoDocument(0, this.itemIndex); + this.layoutDoc.presStatus = "auto"; + this.startPresentation(startSlide); + this.gotoDocument(startSlide, this.itemIndex); + this._presTimer = setInterval(() => { + if (this.itemIndex + 1 < this.childDocs.length) this.next(); + else { + clearInterval(this._presTimer); + this.layoutDoc.presStatus = "manual"; + } + }, targetDoc.presDuration ? NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition) : 2000); } } @@ -234,7 +330,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.updateCurrentPresentation(); this.childDocs.forEach(doc => (doc.presentationTargetDoc as Doc).opacity = 1); this.rootDoc._itemIndex = 0; - this.layoutDoc.presStatus = false; + // this.layoutDoc.presStatus = false; } //The function that starts the presentation, also checking if actions should be applied @@ -249,82 +345,1358 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (doc.presHideAfterButton && this.childDocs.indexOf(doc) < startIndex) { presTargetDoc.opacity = 0; } - if (doc.presFadeButton && this.childDocs.indexOf(doc) < startIndex) { - presTargetDoc.opacity = 0.5; - } }); } - updateMinimize = action((e: React.ChangeEvent, mode: CollectionViewType) => { - if (BoolCast(this.layoutDoc.inOverlay) !== (mode === CollectionViewType.Invalid)) { - if (this.layoutDoc.inOverlay) { - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc); + updateMinimize = () => { + const srcContext = Cast(this.rootDoc.presCollection, Doc, null); + this.turnOffEdit(); + if (srcContext) { + if (srcContext.miniPres) { + document.removeEventListener("keydown", this.minimizeEvents, false); + srcContext.miniPres = false; CollectionDockingView.AddRightSplit(this.rootDoc); - this.layoutDoc.inOverlay = false; } else { - const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - this.rootDoc.x = pt[0];// 500;//e.clientX + 25; - this.rootDoc.y = pt[1];////e.clientY - 25; + document.addEventListener("keydown", this.minimizeEvents, false); + srcContext.miniPres = true; this.props.addDocTab?.(this.rootDoc, "close"); - Doc.AddDocToList((Doc.UserDoc().myOverlayDocuments as Doc), undefined, this.rootDoc); } } - }); + } @undoBatch viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore const viewType = e.target.selectedOptions[0].value as CollectionViewType; viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here - this.updateMinimize(e, this.rootDoc._viewType = viewType); + this.rootDoc._viewType = viewType; + if (viewType === CollectionViewType.Stacking) this.layoutDoc._gridGap = 5; + }); + + @undoBatch + movementChanged = action((movement: string) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + if (movement === 'zoom') { + activeItem.presZoomButton = !activeItem.presZoomButton; + if (activeItem.presZoomButton) activeItem.presMovement = 'Zoom'; + else activeItem.presMovement = 'None'; + activeItem.presNavButton = false; + } else if (movement === 'nav') { + activeItem.presZoomButton = false; + activeItem.presNavButton = !activeItem.presNavButton; + if (activeItem.presNavButton) activeItem.presMovement = 'Pan'; + else activeItem.presMovement = 'None'; + } else if (movement === 'switch') { + targetDoc.presTransition = 0; + activeItem.presSwitchButton = !activeItem.presSwitchButton; + if (activeItem.presSwitchButton) activeItem.presMovement = 'Jump cut'; + else activeItem.presMovement = 'None'; + } else { + activeItem.presMovement = 'None'; + activeItem.presZoomButton = false; + activeItem.presNavButton = false; + activeItem.presSwitchButton = false; + } }); whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); + // For dragging documents into the presentation trail addDocumentFilter = (doc: Doc | Doc[]) => { const docs = doc instanceof Doc ? [doc] : doc; - docs.forEach(doc => { - doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); - !this.childDocs.includes(doc) && (doc.presZoomButton = true); + docs.forEach((doc, i) => { + if (this.childDocs.includes(doc)) { + if (docs.length === i + 1) return false; + } else { + doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); + !this.childDocs.includes(doc) && (doc.presZoomButton = true); + } }); return true; } childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement; removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); - selectElement = (doc: Doc) => this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex)); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight - panelHeight = () => this.props.PanelHeight() - 20; + panelHeight = () => this.props.PanelHeight() - 40; active = (outsideReaction?: boolean) => ((Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground) && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) + // KEYS + @observable _selectedArray: Doc[] = []; + @observable _sortedSelectedArray: Doc[] = []; + @observable _eleArray: HTMLElement[] = []; + @observable _dragArray: HTMLElement[] = []; + + //TODO: Update to radix / quick sort + @action + sortArray = (): Doc[] => { + const sort: Doc[] = this._selectedArray; + this.childDocs.forEach((doc, i) => { + if (this._selectedArray.includes(doc)) { + sort.push(doc); + } + }); + return sort; + } + + @computed get listOfSelected() { + const list = this._selectedArray.map((doc: Doc, index: any) => { + const activeItem = Cast(doc, Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc!, Doc, null); + return ( + <div className="selectedList-items">{index + 1}. {targetDoc.title}</div> + ); + }); + return list; + } + + //Regular click + @action + selectElement = (doc: Doc) => { + this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex)); + } + + //Command click + @action + multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { + this._selectedArray.push(this.childDocs[this.childDocs.indexOf(doc)]); + this._eleArray.push(ref); + this._dragArray.push(drag); + } + + //Shift click + @action + shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { + this._selectedArray = []; + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + if (activeItem) { + for (let i = Math.min(this.itemIndex, this.childDocs.indexOf(doc)); i <= Math.max(this.itemIndex, this.childDocs.indexOf(doc)); i++) { + this._selectedArray.push(this.childDocs[i]); + this._eleArray.push(ref); + this._dragArray.push(drag); + } + } + } + + @action + minimizeEvents = (e: KeyboardEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (e.keyCode === 27) { // Escape key + this.layoutDoc.presStatus = "edit"; + } if (e.keyCode === 37) { // left(37) / a(65) / up(38) to go back + this.back(); + } if (e.keyCode === 39) { // right (39) / d(68) / down(40) to go to next + this.next(); + } + } + + //Esc click + @action + keyEvents = (e: KeyboardEvent) => { + e.stopPropagation(); + e.preventDefault(); + + if (e.keyCode === 27) { // Escape key + if (this.layoutDoc.presStatus === "edit") this._selectedArray = []; + else this.layoutDoc.presStatus = "edit"; + } if ((e.metaKey || e.altKey) && e.keyCode === 65) { // Ctrl-A to select all + if (this.layoutDoc.presStatus === "edit") this._selectedArray = this.childDocs; + } if (e.keyCode === 37) { // left(37) / a(65) / up(38) to go back + if (this.layoutDoc.presStatus !== "edit") this.back(); + } if (e.keyCode === 39) { // right (39) / d(68) / down(40) to go to next + if (this.layoutDoc.presStatus !== "edit") this.next(); + } if (e.keyCode === 32) { // spacebar to 'present' or autoplay + if (this.layoutDoc.presStatus !== "edit") this.startOrResetPres(0); + else this.layoutDoc.presStatus = "manual"; + } + if (e.keyCode === 8) { // delete selected items + if (this.layoutDoc.presStatus === "edit") { + this._selectedArray.forEach((doc, i) => { + this.removeDocument(doc); + }); + } + } + } + + @observable private transitionTools: boolean = false; + @observable private newDocumentTools: boolean = false; + @observable private progressivizeTools: boolean = false; + @observable private moreInfoTools: boolean = false; + @observable private playTools: boolean = false; + @observable private presentTools: boolean = false; + @observable private pathBoolean: boolean = false; + @observable private expandBoolean: boolean = false; + + @undoBatch + @action + viewPaths = async () => { + const srcContext = Cast(this.rootDoc.presCollection, Doc, null); + if (this.pathBoolean) { + if (srcContext) { + this.togglePath(); + srcContext._fitToBox = false; + srcContext._viewType = "freeform"; + srcContext.presPathView = false; + } + } else { + if (srcContext) { + this.togglePath(); + srcContext._fitToBox = true; + srcContext._viewType = "freeform"; + srcContext.presPathView = true; + } + } + const viewType = srcContext?._viewType; + const fit = srcContext?._fitToBox; + } + + @computed get order() { + const order: JSX.Element[] = []; + this.childDocs.forEach((doc, index) => { + const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); + const srcContext = Cast(targetDoc.context, Doc, null); + if (this.rootDoc.presCollection === srcContext) { + order.push( + <div className="pathOrder" style={{ top: NumCast(targetDoc.y), left: NumCast(targetDoc.x) }}> + <div className="pathOrder-frame">{index + 1}</div> + </div>); + } else { + order.push( + <div className="pathOrder" style={{ top: 0, left: 0 }}> + <div className="pathOrder-frame">{index + 1}</div> + </div>); + } + }); + return order; + } + + @computed get paths() { + let pathPoints = ""; + this.childDocs.forEach((doc, index) => { + const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); + const srcContext = Cast(targetDoc.context, Doc, null); + if (targetDoc && this.rootDoc.presCollection === srcContext) { + const n1x = NumCast(targetDoc.x) + (NumCast(targetDoc._width) / 2); + const n1y = NumCast(targetDoc.y) + (NumCast(targetDoc._height) / 2); + if (index = 0) pathPoints = n1x + "," + n1y; + else pathPoints = pathPoints + " " + n1x + "," + n1y; + } else { + if (index = 0) pathPoints = 0 + "," + 0; + else pathPoints = pathPoints + " " + 0 + "," + 0; + } + }); + return (<polyline + points={pathPoints} + style={{ + opacity: 1, + stroke: "#69a6db", + strokeWidth: 5, + strokeDasharray: '10 5', + }} + fill="none" + markerStart="url(#markerSquare)" + markerMid="url(#markerSquare)" + markerEnd="url(#markerArrow)" + />); + } + + @action togglePath = () => this.pathBoolean = !this.pathBoolean; + @action toggleExpand = () => this.expandBoolean = !this.expandBoolean; + + /** + * The function that is called on click to turn fading document after presented option on/off. + * It also makes sure that the option swithches from hide-after to this one, since both + * can't coexist. + */ + @action + onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + activeItem.presFadeButton = !activeItem.presFadeButton; + if (!activeItem.presFadeButton) { + if (targetDoc) { + targetDoc.opacity = 1; + } + } else { + activeItem.presHideAfterButton = false; + if (this.rootDoc.presStatus !== "edit" && targetDoc) { + targetDoc.opacity = 0.5; + } + } + } + + // WHAT IS THIS? + @action + dropdownToggle = (menu: string) => { + const dropMenu = document.getElementById('presBox' + menu + 'Dropdown'); + if (dropMenu) dropMenu.style.display === 'none' ? dropMenu.style.display = 'block' : dropMenu.style.display = 'none'; + } + + setTransitionTime = (number: String) => { + const timeInMS = Number(number) * 1000; + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + if (targetDoc) targetDoc.presTransition = timeInMS; + } + + setDurationTime = (number: String) => { + const timeInMS = Number(number) * 1000; + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + if (targetDoc) targetDoc.presDuration = timeInMS; + } + + @computed get transitionDropdown() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + if (activeItem && targetDoc) { + const transitionSpeed = targetDoc.presTransition ? String(Number(targetDoc.presTransition) / 1000) : 0.5; + const duration = targetDoc.presDuration ? String(Number(targetDoc.presDuration) / 1000) : 2; + const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None'; + activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; + return ( + <div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="ribbon-box"> + Movement + <div className="presBox-dropdown" + onPointerDown={e => e.stopPropagation()} + // onClick={() => this.dropdownToggle('Movement')} + > + {activeItem.presMovement} + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2 }} icon={"angle-down"} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onClick={e => e.stopPropagation()}> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'None' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('none')}>None</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Zoom' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('zoom')}>Pan and Zoom</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('nav')}>Pan</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === 'Jump cut' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.movementChanged('switch')}>Jump cut</div> + </div> + </div> + <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "inline-flex" : "none" }}> + <div className="presBox-subheading" >Transition Speed</div> + <div className="ribbon-property"> {transitionSpeed} s </div> + </div> + <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} /> + <div className={`slider-headers ${activeItem.presMovement === 'Pan' || activeItem.presMovement === 'Zoom' ? "" : "none"}`}> + <div className="slider-text">Fast</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Slow</div> + </div> + </div> + <div className="ribbon-box"> + Visibility {"&"} Duration + <div className="ribbon-doubleButton"> + <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-button ${activeItem.presHideTillShownButton ? "active" : ""}`} onClick={() => activeItem.presHideTillShownButton = !activeItem.presHideTillShownButton}>Hide before</div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-button ${activeItem.presHideAfterButton ? "active" : ""}`} onClick={() => activeItem.presHideAfterButton = !activeItem.presHideAfterButton}>Hide after</div></Tooltip> + </div> + <div className="ribbon-doubleButton" > + <div className="presBox-subheading">Slide Duration</div> + <div className="ribbon-property"> {duration} s </div> + </div> + <input type="range" step="0.1" min="0.1" max="10" value={duration} style={{ transform: 'rotate(0deg)' }} className={"toolbar-slider"} id="duration-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} /> + <div className={"slider-headers"}> + <div className="slider-text">Short</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Long</div> + </div> + </div> + <div className="ribbon-box"> + Effects + <div className="presBox-dropdown" + onPointerDown={e => e.stopPropagation()} + > + {effect} + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2 }} icon={"angle-down"} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onClick={e => e.stopPropagation()}> + <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'None'}>None</div> + <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Fade'}>Fade In</div> + <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Flip'}>Flip</div> + <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Rotate'}>Rotate</div> + <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Bounce'}>Bounce</div> + <div className={'presBox-dropdownOption'} onPointerDown={e => e.stopPropagation()} onClick={() => targetDoc.presEffect = 'Roll'}>Roll</div> + </div> + </div> + <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? "none" : "inline-flex" }}> + <div className="presBox-subheading" >Effect direction</div> + <div className="ribbon-property"> + {this.effectDirection} + </div> + </div> + <div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}> + <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "left" ? "#5a9edd" : "black" }} onClick={() => targetDoc.presEffectDirection = 'left'}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === "right" ? "#5a9edd" : "black" }} onClick={() => targetDoc.presEffectDirection = 'right'}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === "top" ? "#5a9edd" : "black" }} onClick={() => targetDoc.presEffectDirection = 'top'}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === "bottom" ? "#5a9edd" : "black" }} onClick={() => targetDoc.presEffectDirection = 'bottom'}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection ? "solid 2px black" : "solid 2px #5a9edd", borderRadius: "100%" }} onClick={() => targetDoc.presEffectDirection = false}></div></Tooltip> + </div> + </div> + <div className="ribbon-final-box"> + <div className={this._selectedArray.length === 0 ? "ribbon-final-button" : "ribbon-final-button-hidden"} onClick={() => this.applyTo(this._selectedArray)}> + Apply to selected + </div> + <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}> + Apply to all + </div> + </div> + </div> + ); + } + } + + @computed get effectDirection(): string { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + let effect = ''; + switch (targetDoc.presEffectDirection) { + case 'left': effect = "Enter from left"; break; + case 'right': effect = "Enter from right"; break; + case 'top': effect = "Enter from top"; break; + case 'bottom': effect = "Enter from bottom"; break; + default: effect = "Enter from center"; break; + } + return effect; + } + + applyTo = (array: Doc[]) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + array.forEach((doc, index) => { + const curDoc = Cast(doc, Doc, null); + const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); + if (tagDoc && targetDoc) { + tagDoc.presTransition = targetDoc.presTransition; + tagDoc.presDuration = targetDoc.presDuration; + tagDoc.presEffect = targetDoc.presEffect; + } + }); + } + + private inputRef = React.createRef<HTMLInputElement>(); + + @computed get optionsDropdown() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + if (activeItem && targetDoc) { + return ( + <div> + <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="ribbon-box"> + <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? "inline-flex" : "none" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Start automatically</div> + <div className="ribbon-button" style={{ display: "flex", backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Start manually</div> + </div> + <div className="ribbon-doubleButton" style={{ display: "flex" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.openDocument ? "#aedef8" : "" }} onClick={() => activeItem.openDocument = !activeItem.openDocument}>Open document</div> + </div> + <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.WEB ? "inline-flex" : "none" }}> + <div className="ribbon-button" onClick={this.progressivizeText}>Store original website</div> + </div> + </div> + </div> + </div > + ); + } + } + + @computed get newDocumentToolbarDropdown() { + return ( + <div> + <div className={'presBox-toolbar-dropdown'} style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === "edit" ? "inline-flex" : "none" }} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="layout-container" style={{ height: 'max-content' }}> + <div className="layout" style={{ border: this.layout === 'blank' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'blank'; this.createNewSlide(this.layout); })} /> + <div className="layout" style={{ border: this.layout === 'title' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'title'; this.createNewSlide(this.layout); })}> + <div className="title">Title</div> + <div className="subtitle">Subtitle</div> + </div> + <div className="layout" style={{ border: this.layout === 'header' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'header'; this.createNewSlide(this.layout); })}> + <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div> + </div> + <div className="layout" style={{ border: this.layout === 'content' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => { this.layout = 'content'; this.createNewSlide(this.layout); })}> + <div className="title" style={{ alignSelf: 'center' }}>Title</div> + <div className="content">Text goes here</div> + </div> + {/* <div className="layout" style={{ border: this.layout === 'twoColumns' ? 'solid 2px #5b9ddd' : '' }} onClick={() => runInAction(() => { this.layout = 'twoColumns'; this.createNewSlide(this.layout); })}> + <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div> + <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div> + <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div> + </div> */} + </div> + </div> + </div > + ); + } + + @observable openLayouts: boolean = false; + @observable addFreeform: boolean = true; + @observable layout: string = ""; + @observable title: string = ""; + + @computed get newDocumentDropdown() { + return ( + <div> + <div className={"presBox-ribbon"} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="ribbon-box"> + Slide Title: <br></br> + {/* <div className="dropdown-textInput"> */} + <input className="ribbon-textInput" placeholder="..." type="text" name="fname" ref={this.inputRef} onChange={(e) => { + e.stopPropagation(); + runInAction(() => this.title = e.target.value); + }}></input> + {/* </div> */} + </div> + <div className="ribbon-box"> + Choose type: + <div className="ribbon-doubleButton"> + <div title="Text" className={'ribbon-button'} style={{ background: this.addFreeform ? "" : "#aedef8" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div> + <div title="Freeform" className={'ribbon-button'} style={{ background: this.addFreeform ? "#aedef8" : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div> + </div> + </div> + <div className="ribbon-box" style={{ display: this.addFreeform ? "grid" : "none" }}> + Preset layouts: + <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}> + <div className="layout" style={{ border: this.layout === 'blank' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'blank')} /> + <div className="layout" style={{ border: this.layout === 'title' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'title')}> + <div className="title">Title</div> + <div className="subtitle">Subtitle</div> + </div> + <div className="layout" style={{ border: this.layout === 'header' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'header')}> + <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>Section header</div> + </div> + <div className="layout" style={{ border: this.layout === 'content' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'content')}> + <div className="title" style={{ alignSelf: 'center' }}>Title</div> + <div className="content">Text goes here</div> + </div> + <div className="layout" style={{ border: this.layout === 'twoColumns' ? 'solid 2px #5b9ddd' : '' }} onClick={action(() => this.layout = 'twoColumns')}> + <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>Title</div> + <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>Column one text</div> + <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>Column two text</div> + </div> + </div> + <div className="open-layout" onClick={action(() => this.openLayouts = !this.openLayouts)}> + <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"caret-down"} size={"lg"} /> + </div> + </div> + <div className="ribbon-final-box"> + <div className={this.title !== "" && (this.addFreeform && this.layout !== "" || !this.addFreeform) ? "ribbon-final-button-hidden" : "ribbon-final-button"} onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}> + Create New Slide + </div> + </div> + </div> + </div > + ); + } + + createNewSlide = (layout?: string, title?: string, freeform?: boolean) => { + let doc = undefined; + if (layout) doc = this.createTemplate(layout); + if (freeform && layout) doc = this.createTemplate(layout, title); + if (!freeform && !layout) doc = Docs.Create.TextDocument("", { _nativeWidth: 400, _width: 225, title: title }); + const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); + const data = Cast(presCollection.data, listSpec(Doc)); + const presData = Cast(this.rootDoc.data, listSpec(Doc)); + if (data && doc && presData) { + data.push(doc); + DockedFrameRenderer.PinDoc(doc, false); + this.gotoDocument(this.childDocs.length, this.itemIndex); + } + } + + createTemplate = (layout: string, input?: string) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + let x = 0; + let y = 0; + if (activeItem && targetDoc) { + x = NumCast(targetDoc.x); + y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20; + } + let doc = undefined; + const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24pt", }); + const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16pt" }); + const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20pt" }); + const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24pt" }); + const content = Docs.Create.TextDocument("Click to change texte", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" }); + const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14pt" }); + const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14pt" }); + switch (layout) { + case 'blank': + doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y }); + break; + case 'title': + doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + break; + case 'header': + doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + break; + case 'content': + doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + break; + case 'twoColumns': + doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + break; + default: + break; + } + return doc; + } + + // Dropdown that appears for autoplay + @computed get playDropdown() { + return ( + <div className={`dropdown-play ${this.playTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="dropdown-play-button" onClick={() => this.startOrResetPres(this.itemIndex)}> + Present from current slide + </div> + <div className="dropdown-play-button" onClick={() => this.startOrResetPres(0)}> + Present from first slide + </div> + </div> + ); + } + + // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) + @computed get presentDropdown() { + return ( + <div className={`dropdown-play ${this.presentTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="dropdown-play-button" onClick={this.updateMinimize}> + Minimize + </div> + <div className="dropdown-play-button" onClick={() => { this.startOrResetPres(0); this.turnOffEdit(); }}> + Sidebar view + </div> + </div> + ); + } + + // For toggling the options when the user wants to select play + @action togglePlay = () => { this.playTools = !this.playTools; }; + + // For toggling the options when the user wants to select play + @action togglePresent = () => { this.presentTools = !this.presentTools; }; + + // Case in which the document has keyframes to navigate to next key frame + @undoBatch + @action + nextKeyframe = (tagDoc: Doc): void => { + const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); + const currentFrame = Cast(tagDoc.currentFrame, "number", null); + if (currentFrame === undefined) { + tagDoc.currentFrame = 0; + CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); + CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); + } + // let lastFrame: number = 0; + // childDocs.forEach((doc) => { + // if (NumCast(doc.appearFrame) > lastFrame) lastFrame = NumCast(doc.appearFrame); + // }); + CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); + CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); + tagDoc.currentFrame = Math.max(0, (currentFrame || 0) + 1); + tagDoc.lastFrame = Math.max(NumCast(tagDoc.currentFrame), NumCast(tagDoc.lastFrame)); + if (tagDoc.zoomProgressivize) { + const resize = document.getElementById('resizable'); + if (resize) { + resize.style.width = this.checkList(tagDoc, tagDoc["viewfinder-width-indexed"]) + 'px'; + resize.style.height = this.checkList(tagDoc, tagDoc["viewfinder-height-indexed"]) + 'px'; + resize.style.top = this.checkList(tagDoc, tagDoc["viewfinder-top-indexed"]) + 'px'; + resize.style.left = this.checkList(tagDoc, tagDoc["viewfinder-left-indexed"]) + 'px'; + } + } + } + + @undoBatch + @action + prevKeyframe = (tagDoc: Doc): void => { + const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); + const currentFrame = Cast(tagDoc.currentFrame, "number", null); + if (currentFrame === undefined) { + tagDoc.currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); + } + CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); + tagDoc.currentFrame = Math.max(0, (currentFrame || 0) - 1); + if (tagDoc.zoomProgressivize) { + const resize = document.getElementById('resizable'); + if (resize) { + resize.style.width = this.checkList(tagDoc, tagDoc["viewfinder-width-indexed"]) + 'px'; + resize.style.height = this.checkList(tagDoc, tagDoc["viewfinder-height-indexed"]) + 'px'; + resize.style.top = this.checkList(tagDoc, tagDoc["viewfinder-top-indexed"]) + 'px'; + resize.style.left = this.checkList(tagDoc, tagDoc["viewfinder-left-indexed"]) + 'px'; + } + } + } + + @computed get stringType(): string { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + let type: string = ''; + if (activeItem) { + switch (targetDoc.type) { + case DocumentType.PDF: type = "PDF"; break; + case DocumentType.RTF: type = "Text node"; break; + case DocumentType.COL: type = "Collection"; break; + case DocumentType.AUDIO: type = "Audio"; break; + case DocumentType.VID: type = "Video"; break; + case DocumentType.IMG: type = "Image"; break; + default: type = "Other node"; break; + } + } + return type; + } + + @computed get progressivizeDropdown() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + + if (activeItem && targetDoc) { + return ( + <div> + <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="ribbon-box"> + {this.stringType} selected + <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Child documents</div> + <div className="ribbon-button" style={{ display: activeItem.presProgressivize ? "flex" : "none", backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Edit</div> + </div> + <div className="ribbon-doubleButton" style={{ display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.zoomProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeZoom}>Internal zoom</div> + <div className="ribbon-button" style={{ display: activeItem.zoomProgressivize ? "flex" : "none", backgroundColor: targetDoc.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Viewfinder</div> + {/* <div className="ribbon-button" style={{ display: activeItem.zoomProgressivize ? "flex" : "none", backgroundColor: targetDoc.editSnapZoomProgressivize ? "#aedef8" : "" }} onClick={this.editSnapZoomProgressivize}>Snapshot</div> */} + </div> + <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform' ? "inline-flex" : "none" }}> + <div className="ribbon-button" onClick={this.progressivizeText}>Text progressivize</div> + <div className="ribbon-button" style={{ display: activeItem.textProgressivize ? "flex" : "none", backgroundColor: targetDoc.editTextProgressivize ? "#aedef8" : "" }} onClick={this.editTextProgressivize}>Edit</div> + </div> + <div className="ribbon-doubleButton" style={{ display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> + <div className="ribbon-button" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll progressivize</div> + <div className="ribbon-button" style={{ display: activeItem.scrollProgressivize ? "flex" : "none", backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div> + </div> + </div> + <div className="ribbon-final-box" style={{ display: activeItem.zoomProgressivize || activeItem.scrollProgressivize || activeItem.presProgressivize || activeItem.textProgressivize ? "grid" : "none" }}> + Frames + <div className="ribbon-doubleButton"> + <div className="ribbon-frameSelector"> + <div key="back" title="back frame" className="backKeyframe" onClick={e => { e.stopPropagation(); this.prevKeyframe(targetDoc); }}> + <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> + </div> + <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: targetDoc.editing ? "#5a9edd" : "#5a9edd" }} + onClick={action(() => targetDoc.editing = !targetDoc.editing)} > + {NumCast(targetDoc.currentFrame)} + </div> + <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={e => { e.stopPropagation(); this.nextKeyframe(targetDoc); }}> + <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> + </div> + </div> + <Tooltip title={<><div className="dash-tooltip">{"Last frame"}</div></>}><div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div></Tooltip> + </div> + <div className="ribbon-button" style={{ height: 20, backgroundColor: "#5a9edd" }} onClick={() => console.log(" TODO: play frames")}>Play</div> + </div> + </div> + </div> + ); + } + } + + turnOffEdit = () => { + this.childDocs.forEach((doc, index) => { + const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); + targetDoc.editSnapZoomProgressivize = false; + targetDoc.editZoomProgressivize = false; + targetDoc.editScrollProgressivize = false; + if (doc.type === DocumentType.WEB) { + doc.presWebsite = doc.data; + } + }); + } + + //Toggle whether the user edits or not + @action + editSnapZoomProgressivize = (e: React.MouseEvent) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + if (!targetDoc.editSnapZoomProgressivize) { + targetDoc.editSnapZoomProgressivize = true; + } else { + targetDoc.editSnapZoomProgressivize = false; + } + + } + + //Toggle whether the user edits or not + @action + editZoomProgressivize = (e: React.MouseEvent) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + if (!targetDoc.editZoomProgressivize) { + targetDoc.editZoomProgressivize = true; + } else { + targetDoc.editZoomProgressivize = false; + } + } + + //Toggle whether the user edits or not + @action + editScrollProgressivize = (e: React.MouseEvent) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + if (!targetDoc.editScrollProgressivize) { + targetDoc.editScrollProgressivize = true; + } else { + targetDoc.editScrollProgressivize = false; + } + } + + //Progressivize Zoom + @action + progressivizeScroll = (e: React.MouseEvent) => { + e.stopPropagation(); + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + activeItem.scrollProgressivize = !activeItem.scrollProgressivize; + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + targetDoc.scrollProgressivize = !targetDoc.zoomProgressivize; + CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc.currentFrame), true); + if (targetDoc.editScrollProgressivize) { + targetDoc.editScrollProgressivize = false; + targetDoc.currentFrame = 0; + targetDoc.lastFrame = 0; + } + } + + //Progressivize Zoom + @action + progressivizeZoom = (e: React.MouseEvent) => { + e.stopPropagation(); + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + activeItem.zoomProgressivize = !activeItem.zoomProgressivize; + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + targetDoc.zoomProgressivize = !targetDoc.zoomProgressivize; + CollectionFreeFormDocumentView.setupZoom(targetDoc, true); + if (targetDoc.editZoomProgressivize) { + targetDoc.editZoomProgressivize = false; + targetDoc.currentFrame = 0; + targetDoc.lastFrame = 0; + } + } + + //Progressivize Text nodes + @action + editTextProgressivize = (e: React.MouseEvent) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + targetDoc.currentFrame = targetDoc.lastFrame; + if (targetDoc?.editTextProgressivize) { + targetDoc.editTextProgressivize = false; + } else { + targetDoc.editTextProgressivize = true; + } + } + + @action + progressivizeText = (e: React.MouseEvent) => { + e.stopPropagation(); + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + activeItem.presProgressivize = !activeItem.presProgressivize; + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); + targetDoc.presProgressivize = !targetDoc.presProgressivize; + if (activeItem.presProgressivize) { + targetDoc.currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true); + targetDoc.lastFrame = docs.length - 1; + } + } + + //Progressivize Child Docs + @action + editProgressivize = (e: React.MouseEvent) => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + targetDoc.currentFrame = targetDoc.lastFrame; + if (targetDoc?.editProgressivize) { + targetDoc.editProgressivize = false; + } else { + targetDoc.editProgressivize = true; + } + } + + @action + progressivizeChild = (e: React.MouseEvent) => { + e.stopPropagation(); + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); + const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); + if (!activeItem.presProgressivize) { + activeItem.presProgressivize = true; + targetDoc.presProgressivize = true; + targetDoc.currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true); + targetDoc.lastFrame = docs.length - 1; + } else { + targetDoc.editProgressivize = false; + activeItem.presProgressivize = false; + targetDoc.presProgressivize = false; + // docs.forEach((doc, index) => { + // doc.appearFrame = 0; + // }); + targetDoc.currentFrame = 0; + targetDoc.lastFrame = 0; + } + } + + @action + checkMovementLists = (doc: Doc, xlist: any, ylist: any) => { + const x: List<number> = xlist; + const y: List<number> = ylist; + const tags: JSX.Element[] = []; + let pathPoints = ""; //List of all of the pathpoints that need to be added + for (let i = 0; i < x.length - 1; i++) { + if (y[i] || x[i]) { + if (i === 0) pathPoints = (x[i] - 11) + "," + (y[i] + 33); + else pathPoints = pathPoints + " " + (x[i] - 11) + "," + (y[i] + 33); + tags.push(<div className="progressivizeMove-frame" style={{ position: 'absolute', top: y[i], left: x[i] }}>{i}</div>); + } + } + tags.push(<svg style={{ overflow: 'visible', position: 'absolute' }}><polyline + points={pathPoints} + style={{ + position: 'absolute', + opacity: 1, + stroke: "#000000", + strokeWidth: 2, + strokeDasharray: '10 5', + }} + fill="none" + /></svg>); + return tags; + } + + @observable + toggleDisplayMovement = (doc: Doc) => { + if (doc.displayMovement) doc.displayMovement = false; + else doc.displayMovement = true; + } + + private _isDraggingTL = false; + private _isDraggingTR = false; + private _isDraggingBR = false; + private _isDraggingBL = false; + private _isDragging = false; + // private _drag = ""; + + // onPointerDown = (e: React.PointerEvent): void => { + // e.stopPropagation(); + // e.preventDefault(); + // if (e.button === 0) { + // this._drag = e.currentTarget.id; + // console.log(this._drag); + // } + // document.removeEventListener("pointermove", this.onPointerMove); + // document.addEventListener("pointermove", this.onPointerMove); + // document.removeEventListener("pointerup", this.onPointerUp); + // document.addEventListener("pointerup", this.onPointerUp); + // } + + + //Adds event listener so knows pointer is down and moving + onPointerMid = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._isDragging = true; + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + + //Adds event listener so knows pointer is down and moving + onPointerBR = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._isDraggingBR = true; + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + + //Adds event listener so knows pointer is down and moving + onPointerBL = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._isDraggingBL = true; + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + + //Adds event listener so knows pointer is down and moving + onPointerTR = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._isDraggingTR = true; + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + + //Adds event listener so knows pointer is down and moving + onPointerTL = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._isDraggingTL = true; + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + + //Removes all event listeners + onPointerUp = (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._isDraggingTL = false; + this._isDraggingTR = false; + this._isDraggingBL = false; + this._isDraggingBR = false; + this._isDragging = false; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } + + //Adjusts the value in NodeStore + onPointerMove = (e: PointerEvent): void => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const tagDocView = DocumentManager.Instance.getDocumentView(targetDoc); + e.stopPropagation(); + e.preventDefault(); + const doc = document.getElementById('resizable'); + if (doc && tagDocView) { + + const scale = tagDocView.childScaling(); + const scale2 = tagDocView.props.ScreenToLocalTransform().Scale; + let height = doc.offsetHeight; + let width = doc.offsetWidth; + let top = doc.offsetTop; + let left = doc.offsetLeft; + // const newHeightB = height += (e.movementY * NumCast(targetDoc._viewScale)); + // const newHeightT = height -= (e.movementY * NumCast(targetDoc._viewScale)); + // const newWidthR = width += (e.movementX * NumCast(targetDoc._viewScale)); + // const newWidthL = width -= (e.movementX * NumCast(targetDoc._viewScale)); + // const newLeft = left += (e.movementX * NumCast(targetDoc._viewScale)); + // const newTop = top += (e.movementY * NumCast(targetDoc._viewScale)); + // switch (this._drag) { + // case "": break; + // case "resizer-br": + // doc.style.height = newHeightB + 'px'; + // doc.style.width = newWidthR + 'px'; + // break; + // case "resizer-bl": + // doc.style.height = newHeightB + 'px'; + // doc.style.width = newWidthL + 'px'; + // doc.style.left = newLeft + 'px'; + // break; + // case "resizer-tr": + // doc.style.width = newWidthR + 'px'; + // doc.style.height = newHeightT + 'px'; + // doc.style.top = newTop + 'px'; + // case "resizer-tl": + // doc.style.width = newWidthL + 'px'; + // doc.style.height = newHeightT + 'px'; + // doc.style.top = newTop + 'px'; + // doc.style.left = newLeft + 'px'; + // case "resizable": + // doc.style.top = newTop + 'px'; + // doc.style.left = newLeft + 'px'; + // } + //Bottom right + if (this._isDraggingBR) { + const newHeight = height += (e.movementY * scale2); + doc.style.height = newHeight + 'px'; + const newWidth = width += (e.movementX * scale2); + doc.style.width = newWidth + 'px'; + // Bottom left + } else if (this._isDraggingBL) { + const newHeight = height += (e.movementY * scale); + doc.style.height = newHeight + 'px'; + const newWidth = width -= (e.movementX * scale); + doc.style.width = newWidth + 'px'; + const newLeft = left += (e.movementX * scale); + doc.style.left = newLeft + 'px'; + // Top right + } else if (this._isDraggingTR) { + const newWidth = width += (e.movementX * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.width = newWidth + 'px'; + const newHeight = height -= (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.height = newHeight + 'px'; + const newTop = top += (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.top = newTop + 'px'; + // Top left + } else if (this._isDraggingTL) { + const newWidth = width -= (e.movementX * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.width = newWidth + 'px'; + const newHeight = height -= (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.height = newHeight + 'px'; + const newTop = top += (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.top = newTop + 'px'; + const newLeft = left += (e.movementX * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.left = newLeft + 'px'; + } else if (this._isDragging) { + const newTop = top += (e.movementY * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.top = newTop + 'px'; + const newLeft = left += (e.movementX * tagDocView.props.ScreenToLocalTransform().Scale); + doc.style.left = newLeft + 'px'; + } + this.updateList(targetDoc, targetDoc["viewfinder-width-indexed"], width); + this.updateList(targetDoc, targetDoc["viewfinder-height-indexed"], height); + this.updateList(targetDoc, targetDoc["viewfinder-top-indexed"], top); + this.updateList(targetDoc, targetDoc["viewfinder-left-indexed"], left); + } + } + + @action + checkList = (doc: Doc, list: any): number => { + const x: List<number> = list; + if (x && x.length >= NumCast(doc.currentFrame) + 1) { + return x[NumCast(doc.currentFrame)]; + } else { + x.length = NumCast(doc.currentFrame) + 1; + x[NumCast(doc.currentFrame)] = x[NumCast(doc.currentFrame) - 1]; + return x[NumCast(doc.currentFrame)]; + } + + } + + @action + updateList = (doc: Doc, list: any, val: number) => { + const x: List<number> = list; + if (x && x.length >= NumCast(doc.currentFrame) + 1) { + x[NumCast(doc.currentFrame)] = val; + list = x; + } else { + x.length = NumCast(doc.currentFrame) + 1; + x[NumCast(doc.currentFrame)] = val; + list = x; + } + } + + // scale: NumCast(targetDoc._viewScale), + @computed get zoomProgressivizeContainer() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + if (targetDoc.editZoomProgressivize) { + return ( + <> + <div id="resizable" className="resizable" onPointerDown={this.onPointerMid} style={{ width: this.checkList(targetDoc, targetDoc["viewfinder-width-indexed"]), height: this.checkList(targetDoc, targetDoc["viewfinder-height-indexed"]), top: this.checkList(targetDoc, targetDoc["viewfinder-top-indexed"]), left: this.checkList(targetDoc, targetDoc["viewfinder-left-indexed"]), position: 'absolute' }}> + <div className='resizers'> + <div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerTL}></div> + <div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerTR}></div> + <div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerBL}></div> + <div id="resizer-br" className='resizer bottom-right' onPointerDown={this.onPointerBR}></div> + </div> + </div> + </> + ); + } else return null; + } + + @computed get progressivizeChildDocs() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); + const tags: JSX.Element[] = []; + docs.forEach((doc, index) => { + if (doc["x-indexed"] && doc["y-indexed"]) { + tags.push(<div style={{ position: 'absolute', display: doc.displayMovement ? "block" : "none" }}>{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}</div>); + } + tags.push( + <div className="progressivizeButton" onPointerLeave={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc.currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? "#aedff8" : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}> + <div className="progressivizeButton-prev"><FontAwesomeIcon icon={"caret-left"} size={"lg"} onClick={e => { e.stopPropagation(); this.prevAppearFrame(doc, index); }} /></div> + <div className="progressivizeButton-frame">{doc.appearFrame}</div> + <div className="progressivizeButton-next"><FontAwesomeIcon icon={"caret-right"} size={"lg"} onClick={e => { e.stopPropagation(); this.nextAppearFrame(doc, index); }} /></div> + </div>); + }); + return tags; + } + + @undoBatch + @action + nextAppearFrame = (doc: Doc, i: number): void => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const appearFrame = Cast(doc.appearFrame, "number", null); + if (appearFrame === undefined) { + doc.appearFrame = 0; + } + doc.appearFrame = appearFrame + 1; + this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame)); + } + + @undoBatch + @action + prevAppearFrame = (doc: Doc, i: number): void => { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + const appearFrame = Cast(doc.appearFrame, "number", null); + if (appearFrame === undefined) { + doc.appearFrame = 0; + } + doc.appearFrame = Math.max(0, appearFrame - 1); + this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame)); + } + + @action + updateOpacityList = (list: any, frame: number) => { + const x: List<number> = list; + if (x && x.length >= frame) { + for (let i = 0; i < x.length; i++) { + if (i < frame) { + x[i] = 0; + } else if (i >= frame) { + x[i] = 1; + } + } + list = x; + } else { + x.length = frame + 1; + for (let i = 0; i < x.length; i++) { + if (i < frame) { + x[i] = 0; + } else if (i >= frame) { + x[i] = 1; + } + } + list = x; + } + } + + @computed get moreInfoDropdown() { + return (<div></div>); + } + + @computed + get toolbarWidth(): number { + const width = this.props.PanelWidth(); + return width; + } + + @computed get toolbar() { + const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); + if (activeItem) { + return ( + <> + <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}><FontAwesomeIcon icon={"plus"} /> + <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> + </div></Tooltip> + <div className="toolbar-divider" /> + <Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}><div className={`toolbar-button ${this.pathBoolean ? "active" : ""}`}> + <FontAwesomeIcon icon={"exchange-alt"} onClick={this.viewPaths} /> + </div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{this.expandBoolean ? "Minimize all" : "Expand all"}</div></>}> + <div className={`toolbar-button ${this.expandBoolean ? "active" : ""}`} onClick={() => { this.toggleExpand(); this.childDocs.forEach((doc, ind) => { if (this.expandBoolean) doc.presExpandInlineButton = true; else doc.presExpandInlineButton = false; }); }}> + <FontAwesomeIcon icon={"eye"} /> + </div> + </Tooltip> + {/* <div className="toolbar-button"><FontAwesomeIcon title={"Portal"} icon={"eye"} onClick={this.toolbarTest} /></div> */} + <div className="toolbar-divider" /> + {/* <Tooltip title={<><div className="dash-tooltip">{"Transitions"}</div></>}><div className={`toolbar-button ${this.transitionTools ? "active" : ""}`} onClick={this.toggleTransitionTools}> + <FontAwesomeIcon icon={"rocket"} /> + <div style={{ display: this.props.PanelWidth() > 430 ? "block" : "none" }} className="toolbar-buttonText"> Transitions</div> + <FontAwesomeIcon className={`dropdown ${this.transitionTools ? "active" : ""}`} icon={"angle-down"} /> + </div></Tooltip> + <div className="toolbar-divider" /> + <Tooltip title={<><div className="dash-tooltip">{"Progressivize"}</div></>}><div className={`toolbar-button ${this.progressivizeTools ? "active" : ""}`} onClick={this.toggleProgressivize}> + <FontAwesomeIcon icon={"tasks"} /> + <div style={{ display: this.props.PanelWidth() > 430 ? "block" : "none" }} className="toolbar-buttonText"> Progressivize</div> + <FontAwesomeIcon className={`dropdown ${this.progressivizeTools ? "active" : ""}`} icon={"angle-down"} /> + </div></Tooltip> + <div className="toolbar-divider" /> */} + {/* <div className="toolbar-button" style={{ position: 'absolute', right: 23, transform: 'rotate(45deg)', fontSize: 16 }}> + <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"thumbtack"} /> + </div> + <div className={`toolbar-button ${this.moreInfoTools ? "active" : ""}`} onClick={this.toggleMoreInfo}> + <div className={`toolbar-moreInfo ${this.moreInfoTools ? "active" : ""}`}> + <div className="toolbar-moreInfoBall" /> + <div className="toolbar-moreInfoBall" /> + <div className="toolbar-moreInfoBall" /> + </div> + </div> */} + </> + ); + } else { + return ( + <> + <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> + <FontAwesomeIcon icon={"plus"} /> + <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> + </div></Tooltip> + {/* <div className="toolbar-button" style={{ position: 'absolute', right: 23, transform: 'rotate(45deg)', fontSize: 16 }}> + <FontAwesomeIcon className={"toolbar-thumbtack"} icon={"thumbtack"} /> + </div> + <div className={`toolbar-button ${this.moreInfoTools ? "active" : ""}`} onClick={this.toggleMoreInfo}> + <div className={`toolbar-moreInfo ${this.moreInfoTools ? "active" : ""}`}> + <div className="toolbar-moreInfoBall" /> + <div className="toolbar-moreInfoBall" /> + <div className="toolbar-moreInfoBall" /> + </div> + </div> */} + </> + ); + } + } + render() { - // const presOrderedDocs = DocListCast(this.rootDoc.presOrderedDocs); - // if (presOrderedDocs.length != this.childDocs.length || presOrderedDocs.some((pd, i) => pd !== this.childDocs[i])) { - // this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.slice()); - // } - this.childDocs.slice(); // needed to insure that the childDocs are loaded for looking up fields + this.childDocs.slice(); // needed to insure that the childDocs are loaded for looking up fields const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} > + // const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => { + // return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false); + // }; + return <div onPointerOver={this.onPointerOver} onPointerLeave={this.onPointerLeave} className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} > <div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}> <select className="presBox-viewPicker" onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}> - <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Invalid}>Min</option> <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option> - <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Time}>Time</option> <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel}>Slides</option> </select> - <div className="presBox-button" title="Back" style={{ gridColumn: 2 }} onClick={this.back}> - <FontAwesomeIcon icon={"arrow-left"} /> - </div> - <div className="presBox-button" title={"Reset Presentation" + this.layoutDoc.presStatus ? "" : " From Start"} style={{ gridColumn: 3 }} onClick={this.startOrResetPres}> - <FontAwesomeIcon icon={this.layoutDoc.presStatus ? "stop" : "play"} /> - </div> - <div className="presBox-button" title="Next" style={{ gridColumn: 4 }} onClick={this.next}> - <FontAwesomeIcon icon={"arrow-right"} /> + <div className="presBox-presentPanel"> + <span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}> + <div className="presBox-button-left" onClick={() => this.layoutDoc.presStatus = "manual"}><FontAwesomeIcon icon={"play-circle"} /> Present </div> + <div className={`presBox-button-right ${this.presentTools ? "active" : ""}`} onClick={e => { e.stopPropagation; this.togglePresent(); }}> + <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"angle-down"} /> + {this.presentDropdown} + </div> + </span> + <div className="miniPresOverlay" style={{ display: this.layoutDoc.presStatus !== "edit" ? "inline-flex" : "none" }}> + {/* <span className={`presBox-button ${this.layoutDoc.presStatus !== "edit" ? "active" : ""}`}> + <div className="presBox-button-left" onClick={() => this.startOrResetPres(0)}> + <FontAwesomeIcon icon={"clock"} /> + <FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /> + </div> + <div className={`presBox-button-right ${this.playTools ? "active" : ""}`} onClick={e => { e.stopPropagation; this.togglePlay(); }}> + <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.playTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={"angle-down"} /> + {this.playDropdown} + </div> + </span> */} + <div className="miniPres-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> + <div className="miniPres-button" onClick={() => this.startOrResetPres(this.itemIndex)}><FontAwesomeIcon icon={PresBox.Instance.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> + <div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="miniPres-divider"></div> + <div className="miniPres-button-text">Slide {this.itemIndex + 1} / {this.childDocs.length}</div> + <div className="miniPres-divider"></div> + <div className="miniPres-button" onClick={() => this.layoutDoc.presStatus = "edit"}> + <FontAwesomeIcon icon={"times"} /> + </div> + </div> </div> </div> - <div className="presBox-listCont" > + <div id="toolbarContainer" className={`presBox-toolbar ${this.layoutDoc.presStatus === "edit" ? "active" : ""}`}> {this.toolbar} </div> + {this.newDocumentToolbarDropdown} + <div className="presBox-listCont"> {mode !== CollectionViewType.Invalid ? <CollectionView {...this.props} ContainingCollectionDoc={this.props.Document} @@ -346,9 +1718,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } Scripting.addGlobal(function lookupPresBoxField(container: Doc, field: string, data: Doc) { if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data); - if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 50 : 46; + if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 30 : 26; if (field === 'presStatus') return container.presStatus; if (field === '_itemIndex') return container._itemIndex; if (field === 'presBox') return container; return undefined; -}); +});
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 5f0f33351..af4bd77c7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -560,6 +560,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); + appearanceItems.push({ description: "Create progressivized slide...", event: this.progressivizeText, icon: "desktop" }); cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); const options = cm.findByDescription("Options..."); @@ -571,6 +572,66 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._downX = this._downY = Number.NaN; } + progressivizeText = () => { + const list = this.ProseRef?.getElementsByTagName("li"); + const mainBulletText: string[] = []; + const mainBulletList: Doc[] = []; + if (list) { + const newBullets: Doc[] = this.recursiveProgressivize(1, list); + mainBulletList.push.apply(mainBulletList, newBullets); + } + console.log(mainBulletList.length); + const title = Docs.Create.TextDocument(StrCast(this.rootDoc.title), { title: "Title", _width: 800, _height: 70, x: 20, y: -10, _fontSize: '20pt', backgroundColor: "rgba(0,0,0,0)", appearFrame: 0, _fontWeight: 700 }); + mainBulletList.push(title); + const doc = Docs.Create.FreeformDocument(mainBulletList, { + title: StrCast(this.rootDoc.title), + x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10, + _width: 400, _height: 225, _fitToBox: true, + }); + this.props.addDocument?.(doc); + } + + recursiveProgressivize = (nestDepth: number, list: HTMLCollectionOf<HTMLLIElement>, d?: number, y?: number, before?: string): Doc[] => { + const mainBulletList: Doc[] = []; + let b = d ? d : 0; + let yLoc = y ? y : 0; + let nestCount = 0; + let count: string = before ? before : ''; + const fontSize: string = (16 - (nestDepth * 2)) + 'pt'; + const xLoc: number = (nestDepth * 20); + const width: number = 390 - xLoc; + const height: number = 55 - (nestDepth * 5); + Array.from(list).forEach(listItem => { + const mainBullets: number = Number(listItem.getAttribute("data-bulletstyle")); + if (mainBullets === nestDepth) { + if (listItem.childElementCount > 1) { + b++; + nestCount++; + count = before ? count + nestCount + "." : nestCount + "."; + yLoc += height; + const text = listItem.getElementsByTagName("p")[0].innerText; + const length = text.length; + const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _height: height, x: xLoc, y: 10 + (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); + mainBulletList.push(bullet1); + const newList = this.recursiveProgressivize(nestDepth + 1, listItem.getElementsByTagName("li"), b, yLoc, count); + mainBulletList.push.apply(mainBulletList, newList); + b += newList.length; + yLoc += newList.length * (55 - ((nestDepth + 1) * 5)); + } else { + b++; + nestCount++; + count = before ? count + nestCount + "." : nestCount + "."; + yLoc += height; + const text = listItem.innerText; + const length = text.length; + const bullet1 = Docs.Create.TextDocument(count + " " + text, { title: "Slide text", _width: width, _height: height, x: xLoc, y: 10 + (yLoc), _fontSize: fontSize, backgroundColor: "rgba(0,0,0,0)", appearFrame: d ? d : b }); + mainBulletList.push(bullet1); + } + } + }); + return mainBulletList; + } + recordDictation = () => { DictationManager.Controls.listen({ interimHandler: this.setCurrentBulletContent, @@ -1401,6 +1462,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"), pointerEvents: interactive ? undefined : "none", fontSize: Cast(this.layoutDoc._fontSize, "string", null), + fontWeight: Cast(this.layoutDoc._fontWeight, "number", null), fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), transition: "opacity 1s" }} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 264d3e6c6..cfa9a1844 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -9,8 +9,8 @@ import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; -import { createSchema, makeInterface } from "../../../fields/Schema"; -import { ScriptField } from "../../../fields/ScriptField"; +import { createSchema, makeInterface, listSpec } from "../../../fields/Schema"; +import { ScriptField, ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; import { PdfField } from "../../../fields/URLField"; import { TraceMobx, GetEffectiveAcl } from "../../../fields/util"; @@ -347,6 +347,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } @action + scrollToFrame = (duration: number, top: number) => { + this._mainCont.current && smoothScroll(duration, this._mainCont.current, top); + } + + @action scrollToAnnotation = (scrollToAnnotation: Doc) => { if (scrollToAnnotation) { const offset = this.visibleHeight() / 2; diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss index dbe6b0d4f..fa70b2a41 100644 --- a/src/client/views/presentationview/PresElementBox.scss +++ b/src/client/views/presentationview/PresElementBox.scss @@ -1,11 +1,14 @@ .presElementBox-item { - display: inline-block; - background-color: #eeeeee; + display: grid; + grid-template-columns: max-content max-content max-content; + background-color: #d5dce2; + font-family: Roboto; + letter-spacing: normal; + position: relative; pointer-events: all; width: 100%; height: 100%; - outline-color: maroon; - outline-style: dashed; + font-weight: 400; border-radius: 6px; -webkit-touch-callout: none; -webkit-user-select: none; @@ -13,10 +16,36 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; - transition: all .1s; + transition: all .3s; padding: 0px; padding-bottom: 3px; + .presElementBox-highlight { + position: absolute; + transform: translate(-100px, -4px); + z-index: -1; + width: calc(100% + 200px); + height: calc(100% + 8px); + background-color: #AEDDF8; + } + + .presElementBox-highlightTop { + position: absolute; + transform: translate(-100px, -4px); + z-index: -1; + width: calc(100% + 200px); + height: calc(50% + 4px); + } + + .presElementBox-highlightBottom { + position: absolute; + transform: translate(-100px, 0px); + z-index: -1; + top: 50%; + width: calc(100% + 200px); + height: calc(50% + 4px); + } + .documentView-node { position: absolute; z-index: 1; @@ -33,62 +62,82 @@ .presElementBox-item:hover { transition: all .1s; - background: #AAAAAA; + background: #98b7da; border-radius: 6px; } .presElementBox-active { - background: gray; color: black; border-radius: 6px; - box-shadow: black 2px 2px 5px; + border: solid 2px #5B9FDD; } -.presElementBox-closeIcon { - border-radius: 20px; - transform: scale(0.7); - position: absolute; - right: 0; - top: 0; - padding: 8px; -} - - .presElementBox-buttons { - display: flow-root; - position: relative; + display: grid; + grid-template-rows: 15px; + top: 15px; + left: -20; + position: absolute; width: 100%; height: auto; .presElementBox-interaction { - color: gray; - float: left; - padding: 0px; - width: 20px; - height: 20px; + display: none; } .presElementBox-interaction-selected { - color: white; + color: grey; + background-color: rgba(0, 0, 0, 0); float: left; padding: 0px; width: 20px; height: 20px; - border: solid 1px darkgray; } } -.presElementBox-name { +.presElementBox-number { font-size: 12px; + width: 20; + font-weight: 700; + text-align: right; + justify-content: center; + align-content: center; + left: -20; position: absolute; display: inline-block; - width: calc(100% - 45px); + overflow: hidden; +} + +.presElementBox-name { + align-self: center; + font-size: 13px; + font-family: Roboto; + font-weight: 500; + position: relative; + top: 1px; + padding-left: 10px; + padding-right: 10px; + letter-spacing: normal; + max-width: max-content; text-overflow: ellipsis; overflow: hidden; white-space: pre; } +.presElementBox-time { + align-self: center; + position: relative; + top: 2px; + padding-right: 10px; + font-size: 10; + font-weight: 300; + font-family: Roboto; + z-index: 300; + letter-spacing: normal; +} + .presElementBox-embedded { + grid-column: 1/8; position: relative; display: flex; width: auto; @@ -100,51 +149,56 @@ width: 100%; height: 100%; position: absolute; - left: 0; + border-radius: 3px; top: 0; - background: transparent; - z-index: 2; + left: 0; + z-index: 1; + overflow: hidden; } -@media only screen and (max-device-width: 480px) { - .presElementBox-buttons { - display: inline-flex; - position: absolute; - top: 0; - right: 0; - z-index: 3; - width: 50%; - - .presElementBox-interaction { - width: 50; - height: 50; - } - - .presElementBox-interaction-selected { - width: 50; - height: 50; - } - } - - .presElementBox-item { - display: inline-flex; - overflow: hidden; - } - - .presElementBox-closeIcon { - transform: scale(1.5); - right: 10; - top: 10; - } +.presElementBox-closeIcon { + position: absolute; + border-radius: 100%; + z-index: 300; + right: 3px; + top: 3px; + width: 20px; + height: 20px; + display: flex; + font-size: 75%; + background-color: black; + color: white; + justify-content: center; + align-items: center; +} - .presElementBox-name { - font-size: 30px; - top: 10px; - z-index: 3; - width: 50%; - } +.presElementBox-expand { + position: absolute; + border-radius: 100%; + z-index: 300; + right: 26px; + top: 3px; + width: 20px; + height: 20px; + display: flex; + font-size: 75%; + background-color: black; + color: white; + justify-content: center; + align-items: center; +} - .presElementBox-embedded { - transform: translate(0, 90px) scale(1.5); - } +.presElementBox-expand-selected { + position: absolute; + border-radius: 100%; + right: 3px; + bottom: 3px; + width: 20px; + height: 20px; + z-index: 300; + display: flex; + background-color: black; + color: white; + justify-content: center; + align-items: center; }
\ No newline at end of file diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 4c0972736..11ffde9dd 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -5,8 +5,8 @@ import { Doc, DataSym, DocListCast } from "../../../fields/Doc"; import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from "../../../fields/FieldSymbols"; import { createSchema, makeInterface, listSpec } from '../../../fields/Schema'; -import { Cast, NumCast, BoolCast, ScriptCast } from "../../../fields/Types"; -import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero, numberRange } from "../../../Utils"; +import { Cast, NumCast, BoolCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero, numberRange, setupMoveUpEvents } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionView'; import { ViewBoxBaseComponent } from '../DocComponent'; @@ -15,7 +15,10 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./PresElementBox.scss"; import React = require("react"); import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { PresBox } from "../nodes/PresBox"; import { DocumentType } from "../../documents/DocumentTypes"; +import { Tooltip } from "@material-ui/core"; +import { DragManager } from "../../util/DragManager"; export const presSchema = createSchema({ presentationTargetDoc: Doc, @@ -38,19 +41,18 @@ const PresDocument = makeInterface(presSchema, documentSchema); @observer export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDocument>(PresDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } - _heightDisposer: IReactionDisposer | undefined; // these fields are conditionally computed fields on the layout document that take this document as a parameter @computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements) - @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation elemnt template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up - @computed get presStatus() { return BoolCast(this.lookupField("presStatus")); } + @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up + @computed get presStatus() { return StrCast(this.lookupField("presStatus")); } @computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); } @computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); } @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; } componentDidMount() { this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight], - params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 200 : 0), { fireImmediately: true }); + params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); } componentWillUnmount() { this._heightDisposer?.(); @@ -69,7 +71,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc this.targetDoc.opacity = 1; } } else { - if (this.presStatus && this.indexInPres > this.itemIndex && this.targetDoc) { + if (this.presStatus !== "edit" && this.indexInPres > this.itemIndex && this.targetDoc) { this.targetDoc.opacity = 0; } } @@ -90,7 +92,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc } } else { if (this.rootDoc.presFadeButton) this.rootDoc.presFadeButton = false; - if (this.presStatus && this.indexInPres < this.itemIndex && this.targetDoc) { + if (this.presStatus !== "edit" && this.indexInPres < this.itemIndex && this.targetDoc) { this.targetDoc.opacity = 0; } } @@ -125,7 +127,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc } } else { this.rootDoc.presHideAfterButton = false; - if (this.presStatus && (this.indexInPres < this.itemIndex) && this.targetDoc) { + if (this.presStatus !== "edit" && (this.indexInPres < this.itemIndex) && this.targetDoc) { this.targetDoc.opacity = 0.5; } } @@ -166,10 +168,17 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc */ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord]; - embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight); + @action + presExpandDocumentClick = () => { + this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton; + } + + embedHeight = () => 100; + // embedWidth = () => this.props.PanelWidth(); + // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight); embedWidth = () => this.props.PanelWidth() - 20; /** - * The function that is responsible for rendering the a preview or not for this + * The function that is responsible for rendering a preview or not for this * presentation element. */ @computed get renderEmbeddedInline() { @@ -180,7 +189,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]} LibraryPath={emptyPath} fitToBox={true} - backgroundColor={this.props.backgroundColor} + backgroundColor={() => "darkgrey"} rootSelected={returnTrue} addDocument={returnFalse} removeDocument={returnFalse} @@ -207,36 +216,153 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc </div>; } + @computed get duration() { + let durationInS: number; + if (this.targetDoc.presDuration) durationInS = NumCast(this.targetDoc.presDuration) / 1000; + else durationInS = 2; + return "D: " + durationInS + "s"; + } + + @computed get transition() { + let transitionInS: number; + if (this.targetDoc.presTransition) transitionInS = NumCast(this.targetDoc.presTransition) / 1000; + else transitionInS = 0.5; + return "M: " + transitionInS + "s"; + } + + private _itemRef: React.RefObject<HTMLDivElement> = React.createRef(); + private _dragRef: React.RefObject<HTMLDivElement> = React.createRef(); + + headerDown = (e: React.PointerEvent<HTMLDivElement>) => { + const element = document.elementFromPoint(e.clientX, e.clientY)?.parentElement; + e.stopPropagation(); + e.preventDefault(); + if (element) { + console.log(element.className); + if (PresBox.Instance._eleArray.includes(element)) { + setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); + } + } + } + + headerUp = (e: React.PointerEvent<HTMLDivElement>) => { + e.stopPropagation(); + e.preventDefault(); + DragManager.docsBeingDragged = []; + this._highlightTopRef.current!.style.borderBottom = "0px"; + this._highlightBottomRef.current!.style.borderBottom = "0px"; + } + + startDrag = (e: PointerEvent, down: number[], delta: number[]) => { + // const ele: HTMLElement[] = PresBox.Instance._eleArray.map(doc => doc); + const activeItem = this.rootDoc; + const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray().map(doc => doc)); + // let value = this.getValue(this._heading); + // value = typeof value === "string" ? `"${value}"` : value; + const dragItem: HTMLElement[] = []; + PresBox.Instance._dragArray.map(ele => { + const drag = ele; + drag.style.backgroundColor = "#d5dce2"; + drag.style.borderRadius = '5px'; + dragItem.push(drag); + }); + if (activeItem) { + DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY); + activeItem.dragging = true; + return true; + } + return false; + } + + private _highlightTopRef: React.RefObject<HTMLDivElement> = React.createRef(); + private _highlightBottomRef: React.RefObject<HTMLDivElement> = React.createRef(); + + + onPointerTop = (e: React.PointerEvent<HTMLDivElement>) => { + if (DragManager.docsBeingDragged.length > 0) { + this._highlightTopRef.current!.style.borderTop = "solid 2px #5B9FDD"; + } + } + + onPointerBottom = (e: React.PointerEvent<HTMLDivElement>) => { + if (DragManager.docsBeingDragged.length > 0) { + this._highlightBottomRef.current!.style.borderBottom = "solid 2px #5B9FDD"; + } + } + + onPointerLeave = (e: React.PointerEvent<HTMLDivElement>) => { + if (DragManager.docsBeingDragged.length > 0) { + this._highlightBottomRef.current!.style.borderBottom = "0px"; + this._highlightTopRef.current!.style.borderTop = "0px"; + } + } + render() { const treecontainer = this.props.ContainingCollectionDoc?._viewType === CollectionViewType.Tree; - const className = "presElementBox-item" + (this.itemIndex === this.indexInPres ? " presElementBox-active" : ""); + const className = "presElementBox-item" + (PresBox.Instance._selectedArray.includes(this.rootDoc) ? " presElementBox-active" : ""); const pbi = "presElementBox-interaction"; return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : ( <div className={className} key={this.props.Document[Id] + this.indexInPres} + ref={this._itemRef} style={{ outlineWidth: Doc.IsBrushed(this.targetDoc) ? `1px` : "0px", }} - onClick={e => { this.props.focus(this.rootDoc); e.stopPropagation(); }}> - {treecontainer ? (null) : <> - <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); - e.stopPropagation(); - }}>X</button> - <br /> - </>} - <div className="presElementBox-buttons"> + onClick={e => { + e.stopPropagation(); + e.preventDefault(); + // Command/ control click + if (e.ctrlKey || e.metaKey) { + PresBox.Instance.multiSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!); + // Shift click + } else if (e.shiftKey) { + PresBox.Instance.shiftSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!); + // Regular click + } else { + this.props.focus(this.rootDoc); + PresBox.Instance._eleArray = []; + PresBox.Instance._eleArray.push(this._itemRef.current!); + PresBox.Instance._dragArray = []; + PresBox.Instance._dragArray.push(this._dragRef.current!); + } + }} + onPointerDown={this.headerDown} + onPointerUp={this.headerUp} + > + <> + <div className="presElementBox-number"> + {`${this.indexInPres + 1}.`} + </div> + <div ref={this._dragRef} className="presElementBox-name"> + {`${this.targetDoc?.title}`} + </div> + <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presElementBox-time" style={{ display: PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>{this.transition}</div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presElementBox-time" style={{ display: PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>{this.duration}</div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Remove from presentation"}</div></>}><div + className="presElementBox-closeIcon" + // onPointerDown={e => e.stopPropagation()} + onClick={e => { + this.props.removeDocument?.(this.rootDoc); + e.stopPropagation(); + }}> + <FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} /> + </div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}</div></>}><div className={"presElementBox-expand" + (this.rootDoc.presExpandInlineButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.presExpandDocumentClick(); }}> + <FontAwesomeIcon icon={(this.rootDoc.presExpandInlineButton ? "angle-up" : "angle-down")} onPointerDown={e => e.stopPropagation()} /> + </div></Tooltip> + </> + <div ref={this._highlightTopRef} onPointerOver={this.onPointerTop} onPointerLeave={this.onPointerLeave} className="presElementBox-highlightTop" style={{ zIndex: 299, backgroundColor: "rgba(0,0,0,0)" }} /> + <div ref={this._highlightBottomRef} onPointerOver={this.onPointerBottom} onPointerLeave={this.onPointerLeave} className="presElementBox-highlightBottom" style={{ zIndex: 299, backgroundColor: "rgba(0,0,0,0)" }} /> + <div className="presElementBox-highlight" style={{ backgroundColor: PresBox.Instance._selectedArray.includes(this.rootDoc) ? "#AEDDF8" : "rgba(0,0,0,0)" }} /> + <div className="presElementBox-buttons" style={{ display: this.rootDoc.presExpandInlineButton ? "grid" : "none" }}> <button title="Zoom" className={pbi + (this.rootDoc.presZoomButton ? "-selected" : "")} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} onPointerDown={e => e.stopPropagation()} /></button> <button title="Navigate" className={pbi + (this.rootDoc.presNavButton ? "-selected" : "")} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} onPointerDown={e => e.stopPropagation()} /></button> <button title="Hide Before" className={pbi + (this.rootDoc.presHideTillShownButton ? "-selected" : "")} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={"file"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Fade After" className={pbi + (this.rootDoc.presFadeButton ? "-selected" : "")} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button> <button title="Hide After" className={pbi + (this.rootDoc.presHideAfterButton ? "-selected" : "")} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Group With Up" className={pbi + (this.rootDoc.presGroupButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presGroupButton = !this.rootDoc.presGroupButton; }}><FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Progressivize" className={pbi + (this.rootDoc.pres ? "-selected" : "")} onClick={this.progressivize}><FontAwesomeIcon icon={"tasks"} onPointerDown={e => e.stopPropagation()} /></button> - <button title="Expand Inline" className={pbi + (this.rootDoc.presExpandInlineButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton; }}><FontAwesomeIcon icon={"arrow-down"} onPointerDown={e => e.stopPropagation()} /></button> + <button title="Progressivize" className={pbi + (this.rootDoc.presProgressivize ? "-selected" : "")} onClick={this.progressivize}><FontAwesomeIcon icon={"tasks"} onPointerDown={e => e.stopPropagation()} /></button> + <button title="Effect" className={pbi + (this.rootDoc.presEffect ? "-selected" : "")}>E</button> </div> {this.renderEmbeddedInline} </div> ); } -}
\ No newline at end of file +} + +// this.layoutDoc.title !== "pres element template"
\ No newline at end of file diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ad6a9ac1d..6163fedbb 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -627,7 +627,7 @@ export namespace Doc { const zip = new JSZip(); - zip.file("doc.json", docString); + zip.file(doc.title + ".json", docString); // // Generate a directory within the Zip file structure // var img = zip.folder("images"); @@ -639,7 +639,7 @@ export namespace Doc { zip.generateAsync({ type: "blob" }) .then((content: any) => { // Force down of the Zip file - saveAs(content, "download.zip"); + saveAs(content, doc.title + ".zip"); // glr: Possibly change the name of the document to match the title? }); } // diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 452882e09..24b70057a 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -6,8 +6,11 @@ declare module 'cors'; declare module 'webrtc-adapter'; declare module 'bezier-curve'; -declare module 'fit-curve' - +declare module 'fit-curve'; +declare module 'reveal'; +declare module 'react-reveal'; +declare module 'react-reveal/makeCarousel'; +declare module 'react-resizable-rotatable-draggable'; declare module '@react-pdf/renderer' { import * as React from 'react'; diff --git a/tsconfig.json b/tsconfig.json index 803245ca6..b06cec79f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,8 @@ "es2015" ], "typeRoots": [ - "node_modules/@types" + "node_modules/@types", + "./src/typings" ], "types": [ "youtube" |