From 0dc2b16041bf3c774723b00b440369357931c15b Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Mon, 12 Jul 2021 15:51:27 -0400 Subject: some formatting changes --- src/client/views/collections/schemaView/CollectionSchemaView.scss | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/client/views/collections/schemaView/CollectionSchemaView.scss b/src/client/views/collections/schemaView/CollectionSchemaView.scss index b57fee0e4..0662eabaf 100644 --- a/src/client/views/collections/schemaView/CollectionSchemaView.scss +++ b/src/client/views/collections/schemaView/CollectionSchemaView.scss @@ -134,6 +134,13 @@ min-height: 30px; border: 0 !important; } + .rt-tr:nth-of-type(even) { + direction: ltr; + flex: 0 1 auto; + min-height: 30px; + border: 0 !important; + background-color: lightgray; + } .rt-tr { width: 100%; min-height: 30px; -- cgit v1.2.3-70-g09d2 From b5494be46ea1e02b85d24d9405f0d9c60ba2b0f1 Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Sat, 17 Jul 2021 15:34:46 -0400 Subject: some styling changes --- .../views/collections/collectionSchema/CollectionSchemaView.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 0662eabaf..e866ec079 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -134,12 +134,12 @@ min-height: 30px; border: 0 !important; } - .rt-tr:nth-of-type(even) { + .rt-tr-group:nth-of-type(even) { direction: ltr; flex: 0 1 auto; min-height: 30px; border: 0 !important; - background-color: lightgray; + background-color: red; } .rt-tr { width: 100%; -- cgit v1.2.3-70-g09d2 From 6330a8adc6e85bfa5e0b24016e5d76d85bc07e41 Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Wed, 21 Jul 2021 08:56:39 -0400 Subject: fixed date formatting --- .../collectionSchema/CollectionSchemaCells.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index f75179cea..a8d901f4d 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -351,13 +351,16 @@ export class CollectionSchemaDateCell extends CollectionSchemaCell { //} } + // If the cell is not clicked on, render the date normally. Otherwise, render a date picker. render() { - return !this.props.isFocused ? {this._date ? Field.toString(this._date as Field) : "--"} : - this.handleChange(date)} - onChange={date => this.handleChange(date)} - />; + return !this.props.isFocused ? {this._date ? Field.toString(this._date as Field) : "--"} : +
+ this.handleChange(date)} + onChange={date => this.handleChange(date)} + /> +
} } -- cgit v1.2.3-70-g09d2 From 02c5628bdaf43100a6e40baab781342bc27df464 Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Wed, 21 Jul 2021 12:46:13 -0400 Subject: fixed the fact that text, number, and boolean were ignoring input types when accepting input --- .../collectionSchema/CollectionSchemaCells.tsx | 114 +++++++++++++-------- 1 file changed, 69 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index a8d901f4d..8b7fb9be8 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -214,6 +214,55 @@ export class CollectionSchemaCell extends React.Component { positions.pop(); } } + + // handles input procedures for string cells + let stringInput = (value: string, retVal: boolean): void => { + let valueSansQuotes = value; + if (this._isEditing) { + const vsqLength = valueSansQuotes.length; + // get rid of outer quotes + valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0, + valueSansQuotes.charAt(vsqLength - 1) == "\"" ? vsqLength - 1 : vsqLength); + } + let inputAsString = '"'; + // escape any quotes in the string + for (const i of valueSansQuotes) { + if (i == '"') { + inputAsString += '\\"'; + } else { + inputAsString += i; + } + } + // add a closing quote + inputAsString += '"'; + //two options here: we can strip off outer quotes or we can figure out what's going on with the script + const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + } + + // handles input procedure for number cells + let numberInput = (value: string, retVal: boolean): void => { + const inputscript = value.substring(value.startsWith("=") ? 1 : 0); + // if commas are not stripped, the parser only considers the numbers after the last comma + let inputSansCommas = ""; + for (let s of inputscript) { + if (!(s == ",")) { + inputSansCommas += s; + } + } + const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = value.length !== value.length || value.length - 2 !== value.length + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + } + + // handles input procedure for boolean cells + let boolInput = (value: string, retVal: boolean): void => { + const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = value.length !== value.length || value.length - 2 !== value.length + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + } + const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined"; return (
{ } // check if the input is a boolean let inputIsBool: boolean = value == "false" || value == "true"; - // what to do in the case - if (!inputIsNum && !inputIsBool && !value.startsWith("=")) { - // if it's not a number, it's a string, and should be processed as such - // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically - // after each edit - let valueSansQuotes = value; - if (this._isEditing) { - const vsqLength = valueSansQuotes.length; - // get rid of outer quotes - valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0, - valueSansQuotes.charAt(vsqLength - 1) == "\"" ? vsqLength - 1 : vsqLength); - } - let inputAsString = '"'; - // escape any quotes in the string - for (const i of valueSansQuotes) { - if (i == '"') { - inputAsString += '\\"'; - } else { - inputAsString += i; - } - } - // add a closing quote - inputAsString += '"'; - //two options here: we can strip off outer quotes or we can figure out what's going on with the script - const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); - // handle numbers and expressions - } else if (inputIsNum || value.startsWith("=")) { - //TODO: make accept numbers - const inputscript = value.substring(value.startsWith("=") ? 1 : 0); - // if commas are not stripped, the parser only considers the numbers after the last comma - let inputSansCommas = ""; - for (let s of inputscript) { - if (!(s == ",")) { - inputSansCommas += s; - } + + if (type == undefined) { + // what to do in the case + if (!inputIsNum && !inputIsBool && !value.startsWith("=")) { + // if it's not a number, it's a string, and should be processed as such + // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically + // after each edit + stringInput(value, retVal); + // handle numbers and expressions + } else if (inputIsNum || value.startsWith("=")) { + numberInput(value, retVal); + // handle booleans + } else if (inputIsBool) { + boolInput(value, retVal); } - const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - const changeMade = value.length !== value.length || value.length - 2 !== value.length - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); - // handle booleans - } else if (inputIsBool) { - const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - const changeMade = value.length !== value.length || value.length - 2 !== value.length - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + } else if (type == "string") { + stringInput(value, retVal); + } else if (type == "number" && inputIsNum) { + numberInput(value, retVal); + } else if (type == "boolean" && inputIsBool) { + boolInput(value, retVal); } } if (retVal) { -- cgit v1.2.3-70-g09d2 From ee0e2c6c53db65c5524b67ed09369cc0e0e6f173 Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Wed, 21 Jul 2021 16:10:04 -0400 Subject: Fixed minor bugs --- .../collectionSchema/CollectionSchemaCells.tsx | 27 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index 8b7fb9be8..f7dfaaeb4 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -300,8 +300,16 @@ export class CollectionSchemaCell extends React.Component { inputIsNum = false; } } + + let contentsAreNum = true; + for (let s of contents) { + if (isNaN(parseInt(s)) && !(s == ".") && !(s == ",")) { + contentsAreNum = false; + } + } // check if the input is a boolean let inputIsBool: boolean = value == "false" || value == "true"; + let contentsAreBool: boolean = contents == "false" || contents == "true"; if (type == undefined) { // what to do in the case @@ -317,12 +325,23 @@ export class CollectionSchemaCell extends React.Component { } else if (inputIsBool) { boolInput(value, retVal); } + // if the cell type is a string } else if (type == "string") { stringInput(value, retVal); - } else if (type == "number" && inputIsNum) { - numberInput(value, retVal); - } else if (type == "boolean" && inputIsBool) { - boolInput(value, retVal); + // if the cell type is a number + } else if (type == "number") { + if (inputIsNum) { + numberInput(value, retVal); + } else if (!contentsAreNum) { + stringInput("", retVal); + } + // if the cell type is a boolean + } else if (type == "boolean") { + if (inputIsBool) { + boolInput(value, retVal); + } else if (!contentsAreBool) { + stringInput("", retVal); + } } } if (retVal) { -- cgit v1.2.3-70-g09d2 From 0b6a18bc445825ecf69f57b49b002ed5c8a13902 Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Mon, 26 Jul 2021 16:39:06 -0400 Subject: comments --- src/fields/Doc.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index bd0ba3ad7..e51eb44db 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1251,6 +1251,7 @@ export namespace Doc { if (typeof resolved === "object" && !(resolved instanceof Array)) { output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc); } else { + // give the proper types to the data extracted from the JSON const result = toField(resolved, excludeEmptyObjects); if (appendToExisting) { (output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result; -- cgit v1.2.3-70-g09d2 From b3fde759b304f6401595f4e43bb00cc6fa65c915 Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Thu, 29 Jul 2021 17:50:15 -0400 Subject: added message to empty tab screen --- src/client/goldenLayout.js | 3056 ++++++++++---------- src/client/views/MainView.scss | 30 +- .../views/collections/CollectionDockingView.scss | 23 + 3 files changed, 1574 insertions(+), 1535 deletions(-) (limited to 'src') diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 9cfea7f3f..7fca8d9d5 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -82,16 +82,16 @@ return target; }; - /** - * This is based on Paul Irish's shim, but looks quite odd in comparison. Why? - * Because - * a) it shouldn't affect the global requestAnimationFrame function - * b) it shouldn't pass on the time that has passed - * - * @param {Function} fn - * - * @returns {void} - */ + /** + * This is based on Paul Irish's shim, but looks quite odd in comparison. Why? + * Because + * a) it shouldn't affect the global requestAnimationFrame function + * b) it shouldn't pass on the time that has passed + * + * @param {Function} fn + * + * @returns {void} + */ lm.utils.animFrame = function (fn) { return (window.requestAnimationFrame || window.webkitRequestAnimationFrame || @@ -178,16 +178,16 @@ .replace('.', ''); }; - /** - * A basic XSS filter. It is ultimately up to the - * implementing developer to make sure their particular - * applications and usecases are save from cross site scripting attacks - * - * @param {String} input - * @param {Boolean} keepTags - * - * @returns {String} filtered input - */ + /** + * A basic XSS filter. It is ultimately up to the + * implementing developer to make sure their particular + * applications and usecases are save from cross site scripting attacks + * + * @param {String} input + * @param {Boolean} keepTags + * + * @returns {String} filtered input + */ lm.utils.filterXss = function (input, keepTags) { var output = input @@ -206,41 +206,41 @@ } }; - /** - * Removes html tags from a string - * - * @param {String} input - * - * @returns {String} input without tags - */ + /** + * Removes html tags from a string + * + * @param {String} input + * + * @returns {String} input without tags + */ lm.utils.stripTags = function (input) { return $.trim(input.replace(/(<([^>]+)>)/ig, '')); }; - /** - * A generic and very fast EventEmitter - * implementation. On top of emitting the - * actual event it emits an - * - * lm.utils.EventEmitter.ALL_EVENT - * - * event for every event triggered. This allows - * to hook into it and proxy events forwards - * - * @constructor - */ + /** + * A generic and very fast EventEmitter + * implementation. On top of emitting the + * actual event it emits an + * + * lm.utils.EventEmitter.ALL_EVENT + * + * event for every event triggered. This allows + * to hook into it and proxy events forwards + * + * @constructor + */ lm.utils.EventEmitter = function () { this._mSubscriptions = {}; this._mSubscriptions[lm.utils.EventEmitter.ALL_EVENT] = []; - /** - * Listen for events - * - * @param {String} sEvent The name of the event to listen to - * @param {Function} fCallback The callback to execute when the event occurs - * @param {[Object]} oContext The value of the this pointer within the callback function - * - * @returns {void} - */ + /** + * Listen for events + * + * @param {String} sEvent The name of the event to listen to + * @param {Function} fCallback The callback to execute when the event occurs + * @param {[Object]} oContext The value of the this pointer within the callback function + * + * @returns {void} + */ this.on = function (sEvent, fCallback, oContext) { if (!lm.utils.isFunction(fCallback)) { throw new Error('Tried to listen to event ' + sEvent + ' with non-function callback ' + fCallback); @@ -253,14 +253,14 @@ this._mSubscriptions[sEvent].push({ fn: fCallback, ctx: oContext }); }; - /** - * Emit an event and notify listeners - * - * @param {String} sEvent The name of the event - * @param {Mixed} various additional arguments that will be passed to the listener - * - * @returns {void} - */ + /** + * Emit an event and notify listeners + * + * @param {String} sEvent The name of the event + * @param {Mixed} various additional arguments that will be passed to the listener + * + * @returns {void} + */ this.emit = function (sEvent) { var i, ctx, args; @@ -286,15 +286,15 @@ } }; - /** - * Removes a listener for an event, or all listeners if no callback and context is provided. - * - * @param {String} sEvent The name of the event - * @param {Function} fCallback The previously registered callback method (optional) - * @param {Object} oContext The previously registered context (optional) - * - * @returns {void} - */ + /** + * Removes a listener for an event, or all listeners if no callback and context is provided. + * + * @param {String} sEvent The name of the event + * @param {Function} fCallback The previously registered callback method (optional) + * @param {Object} oContext The previously registered context (optional) + * + * @returns {void} + */ this.unbind = function (sEvent, fCallback, oContext) { if (!this._mSubscriptions[sEvent]) { throw new Error('No subscribtions to unsubscribe for event ' + sEvent); @@ -318,28 +318,28 @@ } }; - /** - * Alias for unbind - */ + /** + * Alias for unbind + */ this.off = this.unbind; - /** - * Alias for emit - */ + /** + * Alias for emit + */ this.trigger = this.emit; }; - /** - * The name of the event that's triggered for every other event - * - * usage - * - * myEmitter.on( lm.utils.EventEmitter.ALL_EVENT, function( eventName, argsArray ){ - * //do stuff - * }); - * - * @type {String} - */ + /** + * The name of the event that's triggered for every other event + * + * usage + * + * myEmitter.on( lm.utils.EventEmitter.ALL_EVENT, function( eventName, argsArray ){ + * //do stuff + * }); + * + * @type {String} + */ lm.utils.EventEmitter.ALL_EVENT = '__all'; lm.utils.DragListener = function (eElement, nButtonCode) { lm.utils.EventEmitter.call(this); @@ -349,14 +349,14 @@ this._eBody = $(document.body); this._nButtonCode = nButtonCode || 0; - /** - * The delay after which to start the drag in milliseconds - */ + /** + * The delay after which to start the drag in milliseconds + */ this._nDelay = 200; - /** - * The distance the mouse needs to be moved to qualify as a drag - */ + /** + * The distance the mouse needs to be moved to qualify as a drag + */ this._nDistance = 10;//TODO - works better with delay only this._nX = 0; @@ -459,16 +459,16 @@ }; } }); - /** - * The main class that will be exposed as GoldenLayout. - * - * @public - * @constructor - * @param {GoldenLayout config} config - * @param {[DOM element container]} container Can be a jQuery selector string or a Dom element. Defaults to body - * - * @returns {VOID} - */ + /** + * The main class that will be exposed as GoldenLayout. + * + * @public + * @constructor + * @param {GoldenLayout config} config + * @param {[DOM element container]} container Can be a jQuery selector string or a Dom element. Defaults to body + * + * @returns {VOID} + */ lm.LayoutManager = function (config, container) { if (!$ || typeof $.noConflict !== 'function') { @@ -521,59 +521,59 @@ }; }; - /** - * Hook that allows to access private classes - */ + /** + * Hook that allows to access private classes + */ lm.LayoutManager.__lm = lm; - /** - * Takes a GoldenLayout configuration object and - * replaces its keys and values recursively with - * one letter codes - * - * @static - * @public - * @param {Object} config A GoldenLayout config object - * - * @returns {Object} minified config - */ + /** + * Takes a GoldenLayout configuration object and + * replaces its keys and values recursively with + * one letter codes + * + * @static + * @public + * @param {Object} config A GoldenLayout config object + * + * @returns {Object} minified config + */ lm.LayoutManager.minifyConfig = function (config) { return (new lm.utils.ConfigMinifier()).minifyConfig(config); }; - /** - * Takes a configuration Object that was previously minified - * using minifyConfig and returns its original version - * - * @static - * @public - * @param {Object} minifiedConfig - * - * @returns {Object} the original configuration - */ + /** + * Takes a configuration Object that was previously minified + * using minifyConfig and returns its original version + * + * @static + * @public + * @param {Object} minifiedConfig + * + * @returns {Object} the original configuration + */ lm.LayoutManager.unminifyConfig = function (config) { return (new lm.utils.ConfigMinifier()).unminifyConfig(config); }; lm.utils.copy(lm.LayoutManager.prototype, { - /** - * Register a component with the layout manager. If a configuration node - * of type component is reached it will look up componentName and create the - * associated component - * - * { - * type: "component", - * componentName: "EquityNewsFeed", - * componentState: { "feedTopic": "us-bluechips" } - * } - * - * @public - * @param {String} name - * @param {Function} constructor - * - * @returns {void} - */ + /** + * Register a component with the layout manager. If a configuration node + * of type component is reached it will look up componentName and create the + * associated component + * + * { + * type: "component", + * componentName: "EquityNewsFeed", + * componentState: { "feedTopic": "us-bluechips" } + * } + * + * @public + * @param {String} name + * @param {Function} constructor + * + * @returns {void} + */ registerComponent: function (name, constructor) { if (typeof constructor !== 'function') { throw new Error('Please register a constructor function'); @@ -586,12 +586,12 @@ this._components[name] = constructor; }, - /** - * Creates a layout configuration object based on the the current state - * - * @public - * @returns {Object} GoldenLayout configuration - */ + /** + * Creates a layout configuration object based on the the current state + * + * @public + * @returns {Object} GoldenLayout configuration + */ toConfig: function (root) { var config, next, i; @@ -603,18 +603,18 @@ throw new Error('Root must be a ContentItem'); } - /* - * settings & labels - */ + /* + * settings & labels + */ config = { settings: lm.utils.copy({}, this.config.settings), dimensions: lm.utils.copy({}, this.config.dimensions), labels: lm.utils.copy({}, this.config.labels) }; - /* - * Content - */ + /* + * Content + */ config.content = []; next = function (configNode, item) { var key, i; @@ -641,30 +641,30 @@ next(config, this.root); } - /* - * Retrieve config for subwindows - */ + /* + * Retrieve config for subwindows + */ this._$reconcilePopoutWindows(); config.openPopouts = []; for (i = 0; i < this.openPopouts.length; i++) { config.openPopouts.push(this.openPopouts[i].toConfig()); } - /* - * Add maximised item - */ + /* + * Add maximised item + */ config.maximisedItemId = this._maximisedItem ? '__glMaximised' : null; return config; }, - /** - * Returns a previously registered component - * - * @public - * @param {String} name The name used - * - * @returns {Function} - */ + /** + * Returns a previously registered component + * + * @public + * @param {String} name The name used + * + * @returns {Function} + */ getComponent: function (name) { if (this._components[name] === undefined) { throw new lm.errors.ConfigurationError('Unknown component "' + name + '"'); @@ -673,44 +673,44 @@ return this._components[name]; }, - /** - * Creates the actual layout. Must be called after all initial components - * are registered. Recurses through the configuration and sets up - * the item tree. - * - * If called before the document is ready it adds itself as a listener - * to the document.ready event - * - * @public - * - * @returns {void} - */ + /** + * Creates the actual layout. Must be called after all initial components + * are registered. Recurses through the configuration and sets up + * the item tree. + * + * If called before the document is ready it adds itself as a listener + * to the document.ready event + * + * @public + * + * @returns {void} + */ init: function () { - /** - * Create the popout windows straight away. If popouts are blocked - * an error is thrown on the same 'thread' rather than a timeout and can - * be caught. This also prevents any further initilisation from taking place. - */ + /** + * Create the popout windows straight away. If popouts are blocked + * an error is thrown on the same 'thread' rather than a timeout and can + * be caught. This also prevents any further initilisation from taking place. + */ if (this._subWindowsCreated === false) { this._createSubWindows(); this._subWindowsCreated = true; } - /** - * If the document isn't ready yet, wait for it. - */ + /** + * If the document isn't ready yet, wait for it. + */ if (document.readyState === 'loading' || document.body === null) { $(document).ready(lm.utils.fnBind(this.init, this)); return; } - /** - * If this is a subwindow, wait a few milliseconds for the original - * page's js calls to be executed, then replace the bodies content - * with GoldenLayout - */ + /** + * If this is a subwindow, wait a few milliseconds for the original + * page's js calls to be executed, then replace the bodies content + * with GoldenLayout + */ if (this.isSubWindow === true && this._creationTimeoutPassed === false) { setTimeout(lm.utils.fnBind(this.init, this), 7); this._creationTimeoutPassed = true; @@ -732,15 +732,15 @@ this.emit('initialised'); }, - /** - * Updates the layout managers size - * - * @public - * @param {[int]} width height in pixels - * @param {[int]} height width in pixels - * - * @returns {void} - */ + /** + * Updates the layout managers size + * + * @public + * @param {[int]} width height in pixels + * @param {[int]} height width in pixels + * + * @returns {void} + */ updateSize: function (width, height) { if (arguments.length === 2) { this.width = width; @@ -763,13 +763,13 @@ } }, - /** - * Destroys the LayoutManager instance itself as well as every ContentItem - * within it. After this is called nothing should be left of the LayoutManager. - * - * @public - * @returns {void} - */ + /** + * Destroys the LayoutManager instance itself as well as every ContentItem + * within it. After this is called nothing should be left of the LayoutManager. + * + * @public + * @returns {void} + */ destroy: function () { if (this.isInitialised === false) { return; @@ -793,16 +793,16 @@ this._dragSources = []; }, - /** - * Recursively creates new item tree structures based on a provided - * ItemConfiguration object - * - * @public - * @param {Object} config ItemConfig - * @param {[ContentItem]} parent The item the newly created item should be a child of - * - * @returns {lm.items.ContentItem} - */ + /** + * Recursively creates new item tree structures based on a provided + * ItemConfiguration object + * + * @public + * @param {Object} config ItemConfig + * @param {[ContentItem]} parent The item the newly created item should be a child of + * + * @returns {lm.items.ContentItem} + */ createContentItem: function (config, parent) { var typeErrorMsg, contentItem; @@ -823,9 +823,9 @@ } - /** - * We add an additional stack around every component that's not within a stack anyways. - */ + /** + * We add an additional stack around every component that's not within a stack anyways. + */ if ( // If this is a component config.type === 'component' && @@ -851,17 +851,17 @@ return contentItem; }, - /** - * Creates a popout window with the specified content and dimensions - * - * @param {Object|lm.itemsAbstractContentItem} configOrContentItem - * @param {[Object]} dimensions A map with width, height, left and top - * @param {[String]} parentId the id of the element this item will be appended to - * when popIn is called - * @param {[Number]} indexInParent The position of this item within its parent element + /** + * Creates a popout window with the specified content and dimensions + * + * @param {Object|lm.itemsAbstractContentItem} configOrContentItem + * @param {[Object]} dimensions A map with width, height, left and top + * @param {[String]} parentId the id of the element this item will be appended to + * when popIn is called + * @param {[Number]} indexInParent The position of this item within its parent element - * @returns {lm.controls.BrowserPopout} - */ + * @returns {lm.controls.BrowserPopout} + */ createPopout: function (configOrContentItem, dimensions, parentId, indexInParent) { var config = configOrContentItem, isItem = configOrContentItem instanceof lm.items.AbstractContentItem, @@ -879,14 +879,14 @@ config = this.toConfig(configOrContentItem).content; parentId = lm.utils.getUniqueId(); - /** - * If the item is the only component within a stack or for some - * other reason the only child of its parent the parent will be destroyed - * when the child is removed. - * - * In order to support this we move up the tree until we find something - * that will remain after the item is being popped out - */ + /** + * If the item is the only component within a stack or for some + * other reason the only child of its parent the parent will be destroyed + * when the child is removed. + * + * In order to support this we move up the tree until we find something + * that will remain after the item is being popped out + */ parent = configOrContentItem.parent; child = configOrContentItem; while (parent.contentItems.length === 1 && !parent.isRoot) { @@ -946,16 +946,16 @@ return browserPopout; }, - /** - * Attaches DragListener to any given DOM element - * and turns it into a way of creating new ContentItems - * by 'dragging' the DOM element into the layout - * - * @param {jQuery DOM element} element - * @param {Object|Function} itemConfig for the new item to be created, or a function which will provide it - * - * @returns {void} - */ + /** + * Attaches DragListener to any given DOM element + * and turns it into a way of creating new ContentItems + * by 'dragging' the DOM element into the layout + * + * @param {jQuery DOM element} element + * @param {Object|Function} itemConfig for the new item to be created, or a function which will provide it + * + * @returns {void} + */ createDragSource: function (element, itemConfig) { this.config.settings.constrainDragToContainer = false; var dragSource = new lm.controls.DragSource($(element), itemConfig, this); @@ -964,17 +964,17 @@ return dragSource; }, - /** - * Programmatically selects an item. This deselects - * the currently selected item, selects the specified item - * and emits a selectionChanged event - * - * @param {lm.item.AbstractContentItem} item# - * @param {[Boolean]} _$silent Wheather to notify the item of its selection - * @event selectionChanged - * - * @returns {VOID} - */ + /** + * Programmatically selects an item. This deselects + * the currently selected item, selects the specified item + * and emits a selectionChanged event + * + * @param {lm.item.AbstractContentItem} item# + * @param {[Boolean]} _$silent Wheather to notify the item of its selection + * @event selectionChanged + * + * @returns {VOID} + */ selectItem: function (item, _$silent) { if (this.config.settings.selectionEnabled !== true) { @@ -998,9 +998,9 @@ this.emit('selectionChanged', item); }, - /************************* - * PACKAGE PRIVATE - *************************/ + /************************* + * PACKAGE PRIVATE + *************************/ _$maximiseItem: function (contentItem) { if (this._maximisedItem !== null) { this._$minimiseItem(this._maximisedItem); @@ -1028,20 +1028,20 @@ this.emit('stateChanged'); }, - /** - * This method is used to get around sandboxed iframe restrictions. - * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute - * (as is the case with codepens) the parent window is forbidden from calling certain - * methods on the child, such as window.close() or setting document.location.href. - * - * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call - * _$closeWindow on the child window's gl instance which (after a timeout to disconnect - * the invoking method from the close call) closes itself. - * - * @packagePrivate - * - * @returns {void} - */ + /** + * This method is used to get around sandboxed iframe restrictions. + * If 'allow-top-navigation' is not specified in the iframe's 'sandbox' attribute + * (as is the case with codepens) the parent window is forbidden from calling certain + * methods on the child, such as window.close() or setting document.location.href. + * + * This prevented GoldenLayout popouts from popping in in codepens. The fix is to call + * _$closeWindow on the child window's gl instance which (after a timeout to disconnect + * the invoking method from the close call) closes itself. + * + * @packagePrivate + * + * @returns {void} + */ _$closeWindow: function () { window.setTimeout(function () { window.close(); @@ -1088,13 +1088,13 @@ var i, area, allContentItems = this._getAllContentItems(); this._itemAreas = []; - /** - * If the last item is dragged out, highlight the entire container size to - * allow to re-drop it. allContentItems[ 0 ] === this.root at this point - * - * Don't include root into the possible drop areas though otherwise since it - * will used for every gap in the layout, e.g. splitters - */ + /** + * If the last item is dragged out, highlight the entire container size to + * allow to re-drop it. allContentItems[ 0 ] === this.root at this point + * + * Don't include root into the possible drop areas though otherwise since it + * will used for every gap in the layout, e.g. splitters + */ if (allContentItems.length === 1) { this._itemAreas.push(this.root._$getArea()); return; @@ -1124,18 +1124,18 @@ } }, - /** - * Takes a contentItem or a configuration and optionally a parent - * item and returns an initialised instance of the contentItem. - * If the contentItem is a function, it is first called - * - * @packagePrivate - * - * @param {lm.items.AbtractContentItem|Object|Function} contentItemOrConfig - * @param {lm.items.AbtractContentItem} parent Only necessary when passing in config - * - * @returns {lm.items.AbtractContentItem} - */ + /** + * Takes a contentItem or a configuration and optionally a parent + * item and returns an initialised instance of the contentItem. + * If the contentItem is a function, it is first called + * + * @packagePrivate + * + * @param {lm.items.AbtractContentItem|Object|Function} contentItemOrConfig + * @param {lm.items.AbtractContentItem} parent Only necessary when passing in config + * + * @returns {lm.items.AbtractContentItem} + */ _$normalizeContentItem: function (contentItemOrConfig, parent) { if (!contentItemOrConfig) { throw new Error('No content item defined'); @@ -1158,15 +1158,15 @@ } }, - /** - * Iterates through the array of open popout windows and removes the ones - * that are effectively closed. This is necessary due to the lack of reliably - * listening for window.close / unload events in a cross browser compatible fashion. - * - * @packagePrivate - * - * @returns {void} - */ + /** + * Iterates through the array of open popout windows and removes the ones + * that are effectively closed. This is necessary due to the lack of reliably + * listening for window.close / unload events in a cross browser compatible fashion. + * + * @packagePrivate + * + * @returns {void} + */ _$reconcilePopoutWindows: function () { var openPopouts = [], i; @@ -1185,17 +1185,17 @@ }, - /*************************** - * PRIVATE - ***************************/ - /** - * Returns a flattened array of all content items, - * regardles of level or type - * - * @private - * - * @returns {void} - */ + /*************************** + * PRIVATE + ***************************/ + /** + * Returns a flattened array of all content items, + * regardles of level or type + * + * @private + * + * @returns {void} + */ _getAllContentItems: function () { var allContentItems = []; @@ -1214,13 +1214,13 @@ return allContentItems; }, - /** - * Binds to DOM/BOM events on init - * - * @private - * - * @returns {void} - */ + /** + * Binds to DOM/BOM events on init + * + * @private + * + * @returns {void} + */ _bindEvents: function () { if (this._isFullPage) { $(window).resize(this._resizeFunction); @@ -1228,27 +1228,27 @@ $(window).on('unload beforeunload', this._unloadFunction); }, - /** - * Debounces resize events - * - * @private - * - * @returns {void} - */ + /** + * Debounces resize events + * + * @private + * + * @returns {void} + */ _onResize: function () { clearTimeout(this._resizeTimeoutId); this._resizeTimeoutId = setTimeout(lm.utils.fnBind(this.updateSize, this), 100); }, - /** - * Extends the default config with the user specific settings and applies - * derivations. Please note that there's a seperate method (AbstractContentItem._extendItemNode) - * that deals with the extension of item configs - * - * @param {Object} config - * @static - * @returns {Object} config - */ + /** + * Extends the default config with the user specific settings and applies + * derivations. Please note that there's a seperate method (AbstractContentItem._extendItemNode) + * that deals with the extension of item configs + * + * @param {Object} config + * @static + * @returns {Object} config + */ _createConfig: function (config) { var windowConfigKey = lm.utils.getQueryStringParam('gl-window'); @@ -1283,14 +1283,14 @@ return config; }, - /** - * This is executed when GoldenLayout detects that it is run - * within a previously opened popout window. - * - * @private - * - * @returns {void} - */ + /** + * This is executed when GoldenLayout detects that it is run + * within a previously opened popout window. + * + * @private + * + * @returns {void} + */ _adjustToWindowMode: function () { var popInButton = $('
' + '
' + @@ -1310,26 +1310,26 @@ .css('visibility', 'visible') .append(popInButton); - /* - * This seems a bit pointless, but actually causes a reflow/re-evaluation getting around - * slickgrid's "Cannot find stylesheet." bug in chrome - */ + /* + * This seems a bit pointless, but actually causes a reflow/re-evaluation getting around + * slickgrid's "Cannot find stylesheet." bug in chrome + */ var x = document.body.offsetHeight; // jshint ignore:line - /* - * Expose this instance on the window object - * to allow the opening window to interact with - * it - */ + /* + * Expose this instance on the window object + * to allow the opening window to interact with + * it + */ window.__glInstance = this; }, - /** - * Creates Subwindows (if there are any). Throws an error - * if popouts are blocked. - * - * @returns {void} - */ + /** + * Creates Subwindows (if there are any). Throws an error + * if popouts are blocked. + * + * @returns {void} + */ _createSubWindows: function () { var i, popout; @@ -1345,13 +1345,13 @@ } }, - /** - * Determines what element the layout will be created in - * - * @private - * - * @returns {void} - */ + /** + * Determines what element the layout will be created in + * + * @private + * + * @returns {void} + */ _setContainer: function () { var container = $(this.container || 'body'); @@ -1377,13 +1377,13 @@ this.container = container; }, - /** - * Kicks of the initial, recursive creation chain - * - * @param {Object} config GoldenLayout Config - * - * @returns {void} - */ + /** + * Kicks of the initial, recursive creation chain + * + * @param {Object} config GoldenLayout Config + * + * @returns {void} + */ _create: function (config) { var errorMsg; @@ -1410,12 +1410,12 @@ } }, - /** - * Called when the window is closed or the user navigates away - * from the page - * - * @returns {void} - */ + /** + * Called when the window is closed or the user navigates away + * from the page + * + * @returns {void} + */ _onUnload: function () { if (this.config.settings.closePopoutsOnUnload === true) { for (var i = 0; i < this.openPopouts.length; i++) { @@ -1424,11 +1424,11 @@ } }, - /** - * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth. - * - * @returns {void} - */ + /** + * Adjusts the number of columns to be lower to fit the screen and still maintain minItemWidth. + * + * @returns {void} + */ _adjustColumnsResponsive: function () { // If there is no min width set, or not content items, do nothing. @@ -1470,21 +1470,21 @@ this._updatingColumnsResponsive = false; }, - /** - * Determines if responsive layout should be used. - * - * @returns {bool} - True if responsive layout should be used; otherwise false. - */ + /** + * Determines if responsive layout should be used. + * + * @returns {bool} - True if responsive layout should be used; otherwise false. + */ _useResponsiveLayout: function () { return this.config.settings && (this.config.settings.responsiveMode == 'always' || (this.config.settings.responsiveMode == 'onload' && this._firstLoad)); }, - /** - * Adds all children of a node to another container recursively. - * @param {object} container - Container to add child content items to. - * @param {object} node - Node to search for content items. - * @returns {void} - */ + /** + * Adds all children of a node to another container recursively. + * @param {object} container - Container to add child content items to. + * @param {object} node - Node to search for content items. + * @returns {void} + */ _addChildContentItemsToContainer: function (container, node) { if (node.type === 'stack') { node.contentItems.forEach(function (item) { @@ -1499,10 +1499,10 @@ } }, - /** - * Finds all the stack containers. - * @returns {array} - The found stack containers. - */ + /** + * Finds all the stack containers. + * @returns {array} - The found stack containers. + */ _findAllStackContainers: function () { var stackContainers = []; this._findAllStackContainersRecursive(stackContainers, this.root); @@ -1510,14 +1510,14 @@ return stackContainers; }, - /** - * Finds all the stack containers. - * - * @param {array} - Set of containers to populate. - * @param {object} - Current node to process. - * - * @returns {void} - */ + /** + * Finds all the stack containers. + * + * @param {array} - Set of containers to populate. + * @param {object} - Current node to process. + * + * @returns {void} + */ _findAllStackContainersRecursive: function (stackContainers, node) { node.contentItems.forEach(lm.utils.fnBind(function (item) { if (item.type == 'stack') { @@ -1530,9 +1530,9 @@ } }); - /** - * Expose the Layoutmanager as the single entrypoint using UMD - */ + /** + * Expose the Layoutmanager as the single entrypoint using UMD + */ (function () { /* global define */ if (typeof define === 'function' && define.amd) { @@ -1583,7 +1583,7 @@ close: 'close', maximise: 'maximise', minimise: 'minimise', - popout: 'open in new window', + popout: 'new tab', popin: 'pop in', tabDropdown: 'additional tabs' } @@ -1611,36 +1611,36 @@ lm.utils.copy(lm.container.ItemContainer.prototype, { - /** - * Get the inner DOM element the container's content - * is intended to live in - * - * @returns {DOM element} - */ + /** + * Get the inner DOM element the container's content + * is intended to live in + * + * @returns {DOM element} + */ getElement: function () { return this._contentElement; }, - /** - * Hide the container. Notifies the containers content first - * and then hides the DOM node. If the container is already hidden - * this should have no effect - * - * @returns {void} - */ + /** + * Hide the container. Notifies the containers content first + * and then hides the DOM node. If the container is already hidden + * this should have no effect + * + * @returns {void} + */ hide: function () { this.emit('hide'); this.isHidden = true; this._element.hide(); }, - /** - * Shows a previously hidden container. Notifies the - * containers content first and then shows the DOM element. - * If the container is already visible this has no effect. - * - * @returns {void} - */ + /** + * Shows a previously hidden container. Notifies the + * containers content first and then shows the DOM element. + * If the container is already visible this has no effect. + * + * @returns {void} + */ show: function () { this.emit('show'); this.isHidden = false; @@ -1651,19 +1651,19 @@ } }, - /** - * Set the size from within the container. Traverses up - * the item tree until it finds a row or column element - * and resizes its items accordingly. - * - * If this container isn't a descendant of a row or column - * it returns false - * @todo Rework!!! - * @param {Number} width The new width in pixel - * @param {Number} height The new height in pixel - * - * @returns {Boolean} resizeSuccesful - */ + /** + * Set the size from within the container. Traverses up + * the item tree until it finds a row or column element + * and resizes its items accordingly. + * + * If this container isn't a descendant of a row or column + * it returns false + * @todo Rework!!! + * @param {Number} width The new width in pixel + * @param {Number} height The new height in pixel + * + * @returns {Boolean} resizeSuccesful + */ setSize: function (width, height) { var rowOrColumn = this.parent, rowOrColumnChild = this, @@ -1679,9 +1679,9 @@ rowOrColumn = rowOrColumn.parent; - /** - * No row or column has been found - */ + /** + * No row or column has been found + */ if (rowOrColumn.isRoot) { return false; } @@ -1707,13 +1707,13 @@ return true; }, - /** - * Closes the container if it is closable. Can be called by - * both the component within at as well as the contentItem containing - * it. Emits a close event before the container itself is closed. - * - * @returns {void} - */ + /** + * Closes the container if it is closable. Can be called by + * both the component within at as well as the contentItem containing + * it. Emits a close event before the container itself is closed. + * + * @returns {void} + */ close: function () { if (this._config.isClosable) { this.emit('close'); @@ -1721,55 +1721,55 @@ } }, - /** - * Returns the current state object - * - * @returns {Object} state - */ + /** + * Returns the current state object + * + * @returns {Object} state + */ getState: function () { return this._config.componentState; }, - /** - * Merges the provided state into the current one - * - * @param {Object} state - * - * @returns {void} - */ + /** + * Merges the provided state into the current one + * + * @param {Object} state + * + * @returns {void} + */ extendState: function (state) { this.setState($.extend(true, this.getState(), state)); }, - /** - * Notifies the layout manager of a stateupdate - * - * @param {serialisable} state - */ + /** + * Notifies the layout manager of a stateupdate + * + * @param {serialisable} state + */ setState: function (state) { this._config.componentState = state; this.parent.emitBubblingEvent('stateChanged'); }, - /** - * Set's the components title - * - * @param {String} title - */ + /** + * Set's the components title + * + * @param {String} title + */ setTitle: function (title) { this.parent.setTitle(title); }, - /** - * Set's the containers size. Called by the container's component. - * To set the size programmatically from within the container please - * use the public setSize method - * - * @param {[Int]} width in px - * @param {[Int]} height in px - * - * @returns {void} - */ + /** + * Set's the containers size. Called by the container's component. + * To set the size programmatically from within the container please + * use the public setSize method + * + * @param {[Int]} width in px + * @param {[Int]} height in px + * + * @returns {void} + */ _$setSize: function (width, height) { if (width !== this.width || height !== this.height) { this.width = width; @@ -1784,22 +1784,22 @@ } }); - /** - * Pops a content item out into a new browser window. - * This is achieved by - * - * - Creating a new configuration with the content item as root element - * - Serializing and minifying the configuration - * - Opening the current window's URL with the configuration as a GET parameter - * - GoldenLayout when opened in the new window will look for the GET parameter - * and use it instead of the provided configuration - * - * @param {Object} config GoldenLayout item config - * @param {Object} dimensions A map with width, height, top and left - * @param {String} parentId The id of the element the item will be appended to on popIn - * @param {Number} indexInParent The position of this element within its parent - * @param {lm.LayoutManager} layoutManager - */ + /** + * Pops a content item out into a new browser window. + * This is achieved by + * + * - Creating a new configuration with the content item as root element + * - Serializing and minifying the configuration + * - Opening the current window's URL with the configuration as a GET parameter + * - GoldenLayout when opened in the new window will look for the GET parameter + * and use it instead of the provided configuration + * + * @param {Object} config GoldenLayout item config + * @param {Object} dimensions A map with width, height, top and left + * @param {String} parentId The id of the element the item will be appended to on popIn + * @param {Number} indexInParent The position of this element within its parent + * @param {lm.LayoutManager} layoutManager + */ lm.controls.BrowserPopout = function (config, dimensions, parentId, indexInParent, layoutManager) { lm.utils.EventEmitter.call(this); this.isInitialised = false; @@ -1853,10 +1853,10 @@ } }, - /** - * Returns the popped out item to its original position. If the original - * parent isn't available anymore it falls back to the layout's topmost element - */ + /** + * Returns the popped out item to its original position. If the original + * parent isn't available anymore it falls back to the layout's topmost element + */ popIn: function () { var childConfig, parentItem, @@ -1864,22 +1864,22 @@ if (this._parentId) { - /* - * The $.extend call seems a bit pointless, but it's crucial to - * copy the config returned by this.getGlInstance().toConfig() - * onto a new object. Internet Explorer keeps the references - * to objects on the child window, resulting in the following error - * once the child window is closed: - * - * The callee (server [not server application]) is not available and disappeared - */ + /* + * The $.extend call seems a bit pointless, but it's crucial to + * copy the config returned by this.getGlInstance().toConfig() + * onto a new object. Internet Explorer keeps the references + * to objects on the child window, resulting in the following error + * once the child window is closed: + * + * The callee (server [not server application]) is not available and disappeared + */ childConfig = $.extend(true, {}, this.getGlInstance().toConfig()).content[0]; parentItem = this._layoutManager.root.getItemsById(this._parentId)[0]; - /* - * Fallback if parentItem is not available. Either add it to the topmost - * item or make it the topmost item if the layout is empty - */ + /* + * Fallback if parentItem is not available. Either add it to the topmost + * item or make it the topmost item if the layout is empty + */ if (!parentItem) { if (this._layoutManager.root.contentItems.length > 0) { parentItem = this._layoutManager.root.contentItems[0]; @@ -1894,28 +1894,28 @@ this.close(); }, - /** - * Creates the URL and window parameter - * and opens a new window - * - * @private - * - * @returns {void} - */ + /** + * Creates the URL and window parameter + * and opens a new window + * + * @private + * + * @returns {void} + */ _createWindow: function () { var checkReadyInterval, url = this._createUrl(), - /** - * Bogus title to prevent re-usage of existing window with the - * same title. The actual title will be set by the new window's - * GoldenLayout instance if it detects that it is in subWindowMode - */ + /** + * Bogus title to prevent re-usage of existing window with the + * same title. The actual title will be set by the new window's + * GoldenLayout instance if it detects that it is in subWindowMode + */ title = Math.floor(Math.random() * 1000000).toString(36), - /** - * The options as used in the window.open string - */ + /** + * The options as used in the window.open string + */ options = this._serializeWindowOptions({ width: this._dimensions.width, height: this._dimensions.height, @@ -1946,12 +1946,12 @@ .on('load', lm.utils.fnBind(this._positionWindow, this)) .on('unload beforeunload', lm.utils.fnBind(this._onClose, this)); - /** - * Polling the childwindow to find out if GoldenLayout has been initialised - * doesn't seem optimal, but the alternatives - adding a callback to the parent - * window or raising an event on the window object - both would introduce knowledge - * about the parent to the child window which we'd rather avoid - */ + /** + * Polling the childwindow to find out if GoldenLayout has been initialised + * doesn't seem optimal, but the alternatives - adding a callback to the parent + * window or raising an event on the window object - both would introduce knowledge + * about the parent to the child window which we'd rather avoid + */ checkReadyInterval = setInterval(lm.utils.fnBind(function () { if (this._popoutWindow.__glInstance && this._popoutWindow.__glInstance.isInitialised) { this._onInitialised(); @@ -1960,13 +1960,13 @@ }, this), 10); }, - /** - * Serialises a map of key:values to a window options string - * - * @param {Object} windowOptions - * - * @returns {String} serialised window options - */ + /** + * Serialises a map of key:values to a window options string + * + * @param {Object} windowOptions + * + * @returns {String} serialised window options + */ _serializeWindowOptions: function (windowOptions) { var windowOptionsString = [], key; @@ -1977,12 +1977,12 @@ return windowOptionsString.join(','); }, - /** - * Creates the URL for the new window, including the - * config GET parameter - * - * @returns {String} URL - */ + /** + * Creates the URL for the new window, including the + * config GET parameter + * + * @returns {String} URL + */ _createUrl: function () { var config = { content: this._config }, storageKey = 'gl-window-config-' + lm.utils.getUniqueId(), @@ -2008,57 +2008,57 @@ } }, - /** - * Move the newly created window roughly to - * where the component used to be. - * - * @private - * - * @returns {void} - */ + /** + * Move the newly created window roughly to + * where the component used to be. + * + * @private + * + * @returns {void} + */ _positionWindow: function () { this._popoutWindow.moveTo(this._dimensions.left, this._dimensions.top); this._popoutWindow.focus(); }, - /** - * Callback when the new window is opened and the GoldenLayout instance - * within it is initialised - * - * @returns {void} - */ + /** + * Callback when the new window is opened and the GoldenLayout instance + * within it is initialised + * + * @returns {void} + */ _onInitialised: function () { this.isInitialised = true; this.getGlInstance().on('popIn', this.popIn, this); this.emit('initialised'); }, - /** - * Invoked 50ms after the window unload event - * - * @private - * - * @returns {void} - */ + /** + * Invoked 50ms after the window unload event + * + * @private + * + * @returns {void} + */ _onClose: function () { setTimeout(lm.utils.fnBind(this.emit, this, ['closed']), 50); } }); - /** - * This class creates a temporary container - * for the component whilst it is being dragged - * and handles drag events - * - * @constructor - * @private - * - * @param {Number} x The initial x position - * @param {Number} y The initial y position - * @param {lm.utils.DragListener} dragListener - * @param {lm.LayoutManager} layoutManager - * @param {lm.item.AbstractContentItem} contentItem - * @param {lm.item.AbstractContentItem} originalParent - */ + /** + * This class creates a temporary container + * for the component whilst it is being dragged + * and handles drag events + * + * @constructor + * @private + * + * @param {Number} x The initial x position + * @param {Number} y The initial y position + * @param {lm.utils.DragListener} dragListener + * @param {lm.LayoutManager} layoutManager + * @param {lm.item.AbstractContentItem} contentItem + * @param {lm.item.AbstractContentItem} originalParent + */ lm.controls.DragProxy = function (x, y, dragListener, layoutManager, contentItem, originalParent) { lm.utils.EventEmitter.call(this); @@ -2120,19 +2120,19 @@ lm.utils.copy(lm.controls.DragProxy.prototype, { - /** - * Callback on every mouseMove event during a drag. Determines if the drag is - * still within the valid drag area and calls the layoutManager to highlight the - * current drop area - * - * @param {Number} offsetX The difference from the original x position in px - * @param {Number} offsetY The difference from the original y position in px - * @param {jQuery DOM event} event - * - * @private - * - * @returns {void} - */ + /** + * Callback on every mouseMove event during a drag. Determines if the drag is + * still within the valid drag area and calls the layoutManager to highlight the + * current drop area + * + * @param {Number} offsetX The difference from the original x position in px + * @param {Number} offsetY The difference from the original y position in px + * @param {jQuery DOM event} event + * + * @private + * + * @returns {void} + */ _onDrag: function (offsetX, offsetY, event) { event = event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0] : event; @@ -2148,16 +2148,16 @@ this._setDropPosition(x, y); }, - /** - * Sets the target position, highlighting the appropriate area - * - * @param {Number} x The x position in px - * @param {Number} y The y position in px - * - * @private - * - * @returns {void} - */ + /** + * Sets the target position, highlighting the appropriate area + * + * @param {Number} x The x position in px + * @param {Number} y The y position in px + * + * @private + * + * @returns {void} + */ _setDropPosition: function (x, y) { this.element.css({ left: x, top: y }); this._area = this._layoutManager._$getArea(x, y); @@ -2168,43 +2168,43 @@ } }, - /** - * Callback when the drag has finished. Determines the drop area - * and adds the child to it - * - * @private - * - * @returns {void} - */ + /** + * Callback when the drag has finished. Determines the drop area + * and adds the child to it + * + * @private + * + * @returns {void} + */ _onDrop: function () { this._layoutManager.dropTargetIndicator.hide(); - /* - * Valid drop area found - */ + /* + * Valid drop area found + */ if (this._area !== null) { this._area.contentItem._$onDrop(this._contentItem, this._area); - /** - * No valid drop area available at present, but one has been found before. - * Use it - */ + /** + * No valid drop area available at present, but one has been found before. + * Use it + */ } else if (this._lastValidArea !== null) { this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea); - /** - * No valid drop area found during the duration of the drag. Return - * content item to its original position if a original parent is provided. - * (Which is not the case if the drag had been initiated by createDragSource) - */ + /** + * No valid drop area found during the duration of the drag. Return + * content item to its original position if a original parent is provided. + * (Which is not the case if the drag had been initiated by createDragSource) + */ } else if (this._originalParent) { this._originalParent.addChild(this._contentItem); - /** - * The drag didn't ultimately end up with adding the content item to - * any container. In order to ensure clean up happens, destroy the - * content item. - */ + /** + * The drag didn't ultimately end up with adding the content item to + * any container. In order to ensure clean up happens, destroy the + * content item. + */ } else { this._contentItem._$destroy(); } @@ -2215,18 +2215,18 @@ this._layoutManager.emit('itemDropped', this._contentItem); }, - /** - * Removes the item from its original position within the tree - * - * @private - * - * @returns {void} - */ + /** + * Removes the item from its original position within the tree + * + * @private + * + * @returns {void} + */ _updateTree: function () { - /** - * parent is null if the drag had been initiated by a external drag source - */ + /** + * parent is null if the drag had been initiated by a external drag source + */ if (this._contentItem.parent) { this._contentItem.parent.removeChild(this._contentItem, true); } @@ -2234,13 +2234,13 @@ this._contentItem._$setParent(this); }, - /** - * Updates the Drag Proxie's dimensions - * - * @private - * - * @returns {void} - */ + /** + * Updates the Drag Proxie's dimensions + * + * @private + * + * @returns {void} + */ _setDimensions: function () { var dimensions = this._layoutManager.config.dimensions, width = dimensions.dragProxyWidth, @@ -2259,16 +2259,16 @@ } }); - /** - * Allows for any DOM item to create a component on drag - * start tobe dragged into the Layout - * - * @param {jQuery element} element - * @param {Object} itemConfig the configuration for the contentItem that will be created - * @param {LayoutManager} layoutManager - * - * @constructor - */ + /** + * Allows for any DOM item to create a component on drag + * start tobe dragged into the Layout + * + * @param {jQuery element} element + * @param {Object} itemConfig the configuration for the contentItem that will be created + * @param {LayoutManager} layoutManager + * + * @constructor + */ lm.controls.DragSource = function (element, itemConfig, layoutManager) { this._element = element; this._itemConfig = itemConfig; @@ -2280,11 +2280,11 @@ lm.utils.copy(lm.controls.DragSource.prototype, { - /** - * Called initially and after every drag - * - * @returns {void} - */ + /** + * Called initially and after every drag + * + * @returns {void} + */ _createDragListener: function () { if (this._dragListener !== null) { this._dragListener.destroy(); @@ -2306,14 +2306,14 @@ } }, - /** - * Callback for the DragListener's dragStart event - * - * @param {int} x the x position of the mouse on dragStart - * @param {int} y the x position of the mouse on dragStart - * - * @returns {void} - */ + /** + * Callback for the DragListener's dragStart event + * + * @param {int} x the x position of the mouse on dragStart + * @param {int} y the x position of the mouse on dragStart + * + * @returns {void} + */ _onDragStart: function (x, y) { var itemConfig = this._itemConfig; if (lm.utils.isFunction(itemConfig)) { @@ -2355,12 +2355,12 @@ this.element.hide(); } }); - /** - * This class represents a header above a Stack ContentItem. - * - * @param {lm.LayoutManager} layoutManager - * @param {lm.item.AbstractContentItem} parent - */ + /** + * This class represents a header above a Stack ContentItem. + * + * @param {lm.LayoutManager} layoutManager + * @param {lm.item.AbstractContentItem} parent + */ lm.controls.Header = function (layoutManager, parent) { lm.utils.EventEmitter.call(this); @@ -2400,14 +2400,14 @@ lm.utils.copy(lm.controls.Header.prototype, { - /** - * Creates a new tab and associates it with a contentItem - * - * @param {lm.item.AbstractContentItem} contentItem - * @param {Integer} index The position of the tab - * - * @returns {void} - */ + /** + * Creates a new tab and associates it with a contentItem + * + * @param {lm.item.AbstractContentItem} contentItem + * @param {Integer} index The position of the tab + * + * @returns {void} + */ createTab: function (contentItem, index) { var tab, i; @@ -2441,13 +2441,13 @@ this._updateTabSizes(); }, - /** - * Finds a tab based on the contentItem its associated with and removes it. - * - * @param {lm.item.AbstractContentItem} contentItem - * - * @returns {void} - */ + /** + * Finds a tab based on the contentItem its associated with and removes it. + * + * @param {lm.item.AbstractContentItem} contentItem + * + * @returns {void} + */ removeTab: function (contentItem) { for (var i = 0; i < this.tabs.length; i++) { if (this.tabs[i].contentItem === contentItem) { @@ -2460,11 +2460,11 @@ throw new Error('contentItem is not controlled by this header'); }, - /** - * The programmatical equivalent of clicking a Tab. - * - * @param {lm.item.AbstractContentItem} contentItem - */ + /** + * The programmatical equivalent of clicking a Tab. + * + * @param {lm.item.AbstractContentItem} contentItem + */ setActiveContentItem: function (contentItem) { var i, j, isActive, activeTab; @@ -2478,10 +2478,10 @@ } if (this.layoutManager.config.settings.reorderOnTabMenuClick) { - /** - * If the tab selected was in the dropdown, move everything down one to make way for this one to be the first. - * This will make sure the most used tabs stay visible. - */ + /** + * If the tab selected was in the dropdown, move everything down one to make way for this one to be the first. + * This will make sure the most used tabs stay visible. + */ if (this._lastVisibleTabIndex !== -1 && this.parent.config.activeItemIndex > this._lastVisibleTabIndex) { activeTab = this.tabs[this.parent.config.activeItemIndex]; for (j = this.parent.config.activeItemIndex; j > 0; j--) { @@ -2496,13 +2496,13 @@ this.parent.emitBubblingEvent('stateChanged'); }, - /** - * Programmatically operate with header position. - * - * @param {string} position one of ('top','left','right','bottom') to set or empty to get it. - * - * @returns {string} previous header position - */ + /** + * Programmatically operate with header position. + * + * @param {string} position one of ('top','left','right','bottom') to set or empty to get it. + * + * @returns {string} previous header position + */ position: function (position) { var previous = this.parent._header.show; if (previous && !this.parent._side) @@ -2514,14 +2514,14 @@ return previous; }, - /** - * Programmatically set closability. - * - * @package private - * @param {Boolean} isClosable Whether to enable/disable closability. - * - * @returns {Boolean} Whether the action was successful - */ + /** + * Programmatically set closability. + * + * @package private + * @param {Boolean} isClosable Whether to enable/disable closability. + * + * @returns {Boolean} Whether the action was successful + */ _$setClosable: function (isClosable) { if (this.closeButton && this._isClosable()) { this.closeButton.element[isClosable ? "show" : "hide"](); @@ -2531,13 +2531,13 @@ return false; }, - /** - * Destroys the entire header - * - * @package private - * - * @returns {void} - */ + /** + * Destroys the entire header + * + * @package private + * + * @returns {void} + */ _$destroy: function () { this.emit('destroy', this); @@ -2548,20 +2548,20 @@ this.element.remove(); }, - /** - * get settings from header - * - * @returns {string} when exists - */ + /** + * get settings from header + * + * @returns {string} when exists + */ _getHeaderSetting: function (name) { if (name in this.parent._header) return this.parent._header[name]; }, - /** - * Creates the popout, maximise and close buttons in the header's top right corner - * - * @returns {void} - */ + /** + * Creates the popout, maximise and close buttons in the header's top right corner + * + * @returns {void} + */ _createControls: function () { var closeStack, popout, @@ -2573,26 +2573,26 @@ tabDropdownLabel, showTabDropdown; - /** - * Dropdown to show additional tabs. - */ + /** + * Dropdown to show additional tabs. + */ showTabDropdown = lm.utils.fnBind(this._showAdditionalTabsDropdown, this); tabDropdownLabel = this.layoutManager.config.labels.tabDropdown; this.tabDropdownButton = new lm.controls.HeaderButton(this, tabDropdownLabel, 'lm_tabdropdown', showTabDropdown); this.tabDropdownButton.element.hide(); - /** - * Popout control to launch component in new window. - */ + /** + * Popout control to launch component in new window. + */ if (this._getHeaderSetting('popout')) { popout = lm.utils.fnBind(this._onPopoutClick, this); label = this._getHeaderSetting('popout'); new lm.controls.HeaderButton(this, label, 'lm_popout', popout); } - /** - * Maximise control - set the component to the full size of the layout - */ + /** + * Maximise control - set the component to the full size of the layout + */ if (this._getHeaderSetting('maximise')) { maximise = lm.utils.fnBind(this.parent.toggleMaximise, this.parent); maximiseLabel = this._getHeaderSetting('maximise'); @@ -2608,9 +2608,9 @@ }); } - /** - * Close button - */ + /** + * Close button + */ if (this._isClosable()) { closeStack = lm.utils.fnBind(this.parent.remove, this.parent); label = this._getHeaderSetting('close'); @@ -2618,30 +2618,30 @@ } }, - /** - * Shows drop down for additional tabs when there are too many to display. - * - * @returns {void} - */ + /** + * Shows drop down for additional tabs when there are too many to display. + * + * @returns {void} + */ _showAdditionalTabsDropdown: function () { this.tabDropdownContainer.show(); }, - /** - * Hides drop down for additional tabs when there are too many to display. - * - * @returns {void} - */ + /** + * Hides drop down for additional tabs when there are too many to display. + * + * @returns {void} + */ _hideAdditionalTabsDropdown: function (e) { this.tabDropdownContainer.hide(); }, - /** - * Checks whether the header is closable based on the parent config and - * the global config. - * - * @returns {Boolean} Whether the header is closable. - */ + /** + * Checks whether the header is closable based on the parent config and + * the global config. + * + * @returns {Boolean} Whether the header is closable. + */ _isClosable: function () { return this.parent.config.isClosable && this.layoutManager.config.settings.showCloseIcon; }, @@ -2655,24 +2655,24 @@ }, - /** - * Invoked when the header's background is clicked (not it's tabs or controls) - * - * @param {jQuery DOM event} event - * - * @returns {void} - */ + /** + * Invoked when the header's background is clicked (not it's tabs or controls) + * + * @param {jQuery DOM event} event + * + * @returns {void} + */ _onHeaderClick: function (event) { if (event.target === this.element[0]) { this.parent.select(); } }, - /** - * Pushes the tabs to the tab dropdown if the available space is not sufficient - * - * @returns {void} - */ + /** + * Pushes the tabs to the tab dropdown if the available space is not sufficient + * + * @returns {void} + */ _updateTabSizes: function (showTabMenu) { if (this.tabs.length === 0) { return; @@ -2835,14 +2835,14 @@ } }); - /** - * Represents an individual tab within a Stack's header - * - * @param {lm.controls.Header} header - * @param {lm.items.AbstractContentItem} contentItem - * - * @constructor - */ + /** + * Represents an individual tab within a Stack's header + * + * @param {lm.controls.Header} header + * @param {lm.items.AbstractContentItem} contentItem + * + * @constructor + */ lm.controls.Tab = function (header, contentItem) { this.header = header; this.contentItem = contentItem; @@ -2888,37 +2888,37 @@ } }; - /** - * The tab's html template - * - * @type {String} - */ + /** + * The tab's html template + * + * @type {String} + */ lm.controls.Tab._template = '
  • ' + '
    ' + '
  • '; lm.utils.copy(lm.controls.Tab.prototype, { - /** - * Sets the tab's title to the provided string and sets - * its title attribute to a pure text representation (without - * html tags) of the same string. - * - * @public - * @param {String} title can contain html - */ + /** + * Sets the tab's title to the provided string and sets + * its title attribute to a pure text representation (without + * html tags) of the same string. + * + * @public + * @param {String} title can contain html + */ setTitle: function (title) { this.element.attr('title', lm.utils.stripTags(title)); this.titleElement.html(title); }, - /** - * Sets this tab's active state. To programmatically - * switch tabs, use header.setActiveContentItem( item ) instead. - * - * @public - * @param {Boolean} isActive - */ + /** + * Sets this tab's active state. To programmatically + * switch tabs, use header.setActiveContentItem( item ) instead. + * + * @public + * @param {Boolean} isActive + */ setActive: function (isActive) { if (isActive === this.isActive) { return; @@ -2932,12 +2932,12 @@ } }, - /** - * Destroys the tab - * - * @private - * @returns {void} - */ + /** + * Destroys the tab + * + * @private + * @returns {void} + */ _$destroy: function () { this._layoutManager.emit('tabDestroyed', this); this.element.off('mousedown touchstart', this._onTabClickFn); @@ -2950,15 +2950,15 @@ this.element.remove(); }, - /** - * Callback for the DragListener - * - * @param {Number} x The tabs absolute x position - * @param {Number} y The tabs absolute y position - * - * @private - * @returns {void} - */ + /** + * Callback for the DragListener + * + * @param {Number} x The tabs absolute x position + * @param {Number} y The tabs absolute y position + * + * @private + * @returns {void} + */ _onDragStart: function (x, y) { if (this.contentItem.parent.isMaximised === true) { this.contentItem.parent.toggleMaximise(); @@ -2973,14 +2973,14 @@ ); }, - /** - * Callback when the tab is clicked - * - * @param {jQuery DOM event} event - * - * @private - * @returns {void} - */ + /** + * Callback when the tab is clicked + * + * @param {jQuery DOM event} event + * + * @private + * @returns {void} + */ _onTabClick: function (event) { // left mouse button or tap if (event.button === 0 || event.type === 'touchstart') { @@ -2995,30 +2995,30 @@ } }, - /** - * Callback when the tab's close button is - * clicked - * - * @param {jQuery DOM event} event - * - * @private - * @returns {void} - */ + /** + * Callback when the tab's close button is + * clicked + * + * @param {jQuery DOM event} event + * + * @private + * @returns {void} + */ _onCloseClick: function (event) { event.stopPropagation(); this.header.parent.removeChild(this.contentItem); }, - /** - * Callback to capture tab close button mousedown - * to prevent tab from activating. - * - * @param (jQuery DOM event) event - * - * @private - * @returns {void} - */ + /** + * Callback to capture tab close button mousedown + * to prevent tab from activating. + * + * @param (jQuery DOM event) event + * + * @private + * @returns {void} + */ _onCloseMousedown: function (event) { event.stopPropagation(); } @@ -3040,9 +3040,9 @@ }, transitionElements: function (fromElement, toElement) { - /** - * TODO - This is not quite as cool as expected. Review. - */ + /** + * TODO - This is not quite as cool as expected. Review. + */ return; this._toElement = toElement; this._animationStartTime = lm.utils.now(); @@ -3096,27 +3096,27 @@ lm.errors.ConfigurationError.prototype = new Error(); - /** - * This is the baseclass that all content items inherit from. - * Most methods provide a subset of what the sub-classes do. - * - * It also provides a number of functions for tree traversal - * - * @param {lm.LayoutManager} layoutManager - * @param {item node configuration} config - * @param {lm.item} parent - * - * @event stateChanged - * @event beforeItemDestroyed - * @event itemDestroyed - * @event itemCreated - * @event componentCreated - * @event rowCreated - * @event columnCreated - * @event stackCreated - * - * @constructor - */ + /** + * This is the baseclass that all content items inherit from. + * Most methods provide a subset of what the sub-classes do. + * + * It also provides a number of functions for tree traversal + * + * @param {lm.LayoutManager} layoutManager + * @param {item node configuration} config + * @param {lm.item} parent + * + * @event stateChanged + * @event beforeItemDestroyed + * @event itemDestroyed + * @event itemCreated + * @event componentCreated + * @event rowCreated + * @event columnCreated + * @event stackCreated + * + * @constructor + */ lm.items.AbstractContentItem = function (layoutManager, config, parent) { lm.utils.EventEmitter.call(this); @@ -3146,26 +3146,26 @@ lm.utils.copy(lm.items.AbstractContentItem.prototype, { - /** - * Set the size of the component and its children, called recursively - * - * @abstract - * @returns void - */ + /** + * Set the size of the component and its children, called recursively + * + * @abstract + * @returns void + */ setSize: function () { throw new Error('Abstract Method'); }, - /** - * Calls a method recursively downwards on the tree - * - * @param {String} functionName the name of the function to be called - * @param {[Array]}functionArguments optional arguments that are passed to every function - * @param {[bool]} bottomUp Call methods from bottom to top, defaults to false - * @param {[bool]} skipSelf Don't invoke the method on the class that calls it, defaults to false - * - * @returns {void} - */ + /** + * Calls a method recursively downwards on the tree + * + * @param {String} functionName the name of the function to be called + * @param {[Array]}functionArguments optional arguments that are passed to every function + * @param {[bool]} bottomUp Call methods from bottom to top, defaults to false + * @param {[bool]} skipSelf Don't invoke the method on the class that calls it, defaults to false + * + * @returns {void} + */ callDownwards: function (functionName, functionArguments, bottomUp, skipSelf) { var i; @@ -3180,53 +3180,53 @@ } }, - /** - * Removes a child node (and its children) from the tree - * - * @param {lm.items.ContentItem} contentItem - * - * @returns {void} - */ + /** + * Removes a child node (and its children) from the tree + * + * @param {lm.items.ContentItem} contentItem + * + * @returns {void} + */ removeChild: function (contentItem, keepChild) { - /* - * Get the position of the item that's to be removed within all content items this node contains - */ + /* + * Get the position of the item that's to be removed within all content items this node contains + */ var index = lm.utils.indexOf(contentItem, this.contentItems); - /* - * Make sure the content item to be removed is actually a child of this item - */ + /* + * Make sure the content item to be removed is actually a child of this item + */ if (index === -1) { throw new Error('Can\'t remove child item. Unknown content item'); } - /** - * Call ._$destroy on the content item. This also calls ._$destroy on all its children - */ + /** + * Call ._$destroy on the content item. This also calls ._$destroy on all its children + */ if (keepChild !== true) { this.contentItems[index]._$destroy(); } - /** - * Remove the content item from this nodes array of children - */ + /** + * Remove the content item from this nodes array of children + */ this.contentItems.splice(index, 1); - /** - * Remove the item from the configuration - */ + /** + * Remove the item from the configuration + */ this.config.content.splice(index, 1); - /** - * If this node still contains other content items, adjust their size - */ + /** + * If this node still contains other content items, adjust their size + */ if (this.contentItems.length > 0) { this.callDownwards('setSize'); - /** - * If this was the last content item, remove this node as well - */ + /** + * If this was the last content item, remove this node as well + */ } else if (!(this instanceof lm.items.Root) && this.config.isClosable === true) { const stack = this; const rowOrCol = stack.parent; @@ -3244,14 +3244,14 @@ } }, - /** - * Sets up the tree structure for the newly added child - * The responsibility for the actual DOM manipulations lies - * with the concrete item - * - * @param {lm.items.AbstractContentItem} contentItem - * @param {[Int]} index If omitted item will be appended - */ + /** + * Sets up the tree structure for the newly added child + * The responsibility for the actual DOM manipulations lies + * with the concrete item + * + * @param {lm.items.AbstractContentItem} contentItem + * @param {[Int]} index If omitted item will be appended + */ addChild: function (contentItem, index) { if (index === undefined) { index = this.contentItems.length; @@ -3271,15 +3271,15 @@ } }, - /** - * Replaces oldChild with newChild. This used to use jQuery.replaceWith... which for - * some reason removes all event listeners, so isn't really an option. - * - * @param {lm.item.AbstractContentItem} oldChild - * @param {lm.item.AbstractContentItem} newChild - * - * @returns {void} - */ + /** + * Replaces oldChild with newChild. This used to use jQuery.replaceWith... which for + * some reason removes all event listeners, so isn't really an option. + * + * @param {lm.item.AbstractContentItem} oldChild + * @param {lm.item.AbstractContentItem} newChild + * + * @returns {void} + */ replaceChild: function (oldChild, newChild, _$destroyOldChild) { newChild = this.layoutManager._$normalizeContentItem(newChild); @@ -3293,23 +3293,23 @@ parentNode.replaceChild(newChild.element[0], oldChild.element[0]); - /* - * Optionally destroy the old content item - */ + /* + * Optionally destroy the old content item + */ if (_$destroyOldChild === true) { oldChild.parent = null; oldChild._$destroy(); } - /* - * Wire the new contentItem into the tree - */ + /* + * Wire the new contentItem into the tree + */ this.contentItems[index] = newChild; newChild.parent = this; - /* - * Update tab reference - */ + /* + * Update tab reference + */ if (this.isStack) { this.header.tabs[index].contentItem = newChild; } @@ -3322,33 +3322,33 @@ this.callDownwards('setSize'); }, - /** - * Convenience method. - * Shorthand for this.parent.removeChild( this ) - * - * @returns {void} - */ + /** + * Convenience method. + * Shorthand for this.parent.removeChild( this ) + * + * @returns {void} + */ remove: function () { this.parent.removeChild(this); }, - /** - * Removes the component from the layout and creates a new - * browser window with the component and its children inside - * - * @returns {lm.controls.BrowserPopout} - */ + /** + * Removes the component from the layout and creates a new + * browser window with the component and its children inside + * + * @returns {lm.controls.BrowserPopout} + */ popout: function () { var browserPopout = this.layoutManager.createPopout(this); this.emitBubblingEvent('stateChanged'); return browserPopout; }, - /** - * Maximises the Item or minimises it if it is already maximised - * - * @returns {void} - */ + /** + * Maximises the Item or minimises it if it is already maximised + * + * @returns {void} + */ toggleMaximise: function (e) { e && e.preventDefault(); if (this.isMaximised === true) { @@ -3361,11 +3361,11 @@ this.emitBubblingEvent('stateChanged'); }, - /** - * Selects the item if it is not already selected - * - * @returns {void} - */ + /** + * Selects the item if it is not already selected + * + * @returns {void} + */ select: function () { if (this.layoutManager.selectedItem !== this) { this.layoutManager.selectItem(this, true); @@ -3373,11 +3373,11 @@ } }, - /** - * De-selects the item if it is selected - * - * @returns {void} - */ + /** + * De-selects the item if it is selected + * + * @returns {void} + */ deselect: function () { if (this.layoutManager.selectedItem === this) { this.layoutManager.selectedItem = null; @@ -3385,28 +3385,28 @@ } }, - /** - * Set this component's title - * - * @public - * @param {String} title - * - * @returns {void} - */ + /** + * Set this component's title + * + * @public + * @param {String} title + * + * @returns {void} + */ setTitle: function (title) { this.config.title = title; this.emit('titleChanged', title); this.emit('stateChanged'); }, - /** - * Checks whether a provided id is present - * - * @public - * @param {String} id - * - * @returns {Boolean} isPresent - */ + /** + * Checks whether a provided id is present + * + * @public + * @param {String} id + * + * @returns {Boolean} isPresent + */ hasId: function (id) { if (!this.config.id) { return false; @@ -3417,15 +3417,15 @@ } }, - /** - * Adds an id. Adds it as a string if the component doesn't - * have an id yet or creates/uses an array - * - * @public - * @param {String} id - * - * @returns {void} - */ + /** + * Adds an id. Adds it as a string if the component doesn't + * have an id yet or creates/uses an array + * + * @public + * @param {String} id + * + * @returns {void} + */ addId: function (id) { if (this.hasId(id)) { return; @@ -3440,15 +3440,15 @@ } }, - /** - * Removes an existing id. Throws an error - * if the id is not present - * - * @public - * @param {String} id - * - * @returns {void} - */ + /** + * Removes an existing id. Throws an error + * if the id is not present + * + * @public + * @param {String} id + * + * @returns {void} + */ removeId: function (id) { if (!this.hasId(id)) { throw new Error('Id not found'); @@ -3462,9 +3462,9 @@ } }, - /**************************************** - * SELECTOR - ****************************************/ + /**************************************** + * SELECTOR + ****************************************/ getItemsByFilter: function (filter) { var result = [], next = function (contentItem) { @@ -3508,9 +3508,9 @@ return instances; }, - /**************************************** - * PACKAGE PRIVATE - ****************************************/ + /**************************************** + * PACKAGE PRIVATE + ****************************************/ _$getItemsByProperty: function (key, value) { return this.getItemsByFilter(function (item) { return item[key] === value; @@ -3555,11 +3555,11 @@ } }, - /** - * Destroys this item ands its children - * - * @returns {void} - */ + /** + * Destroys this item ands its children + * + * @returns {void} + */ _$destroy: function () { this.emitBubblingEvent('beforeItemDestroyed'); this.callDownwards('_$destroy', [], true, true); @@ -3567,17 +3567,17 @@ this.emitBubblingEvent('itemDestroyed'); }, - /** - * Returns the area the component currently occupies in the format - * - * { - * x1: int - * xy: int - * y1: int - * y2: int - * contentItem: contentItem - * } - */ + /** + * Returns the area the component currently occupies in the format + * + * { + * x1: int + * xy: int + * y1: int + * y2: int + * contentItem: contentItem + * } + */ _$getArea: function (element) { element = element || this.element; @@ -3595,17 +3595,17 @@ }; }, - /** - * The tree of content items is created in two steps: First all content items are instantiated, - * then init is called recursively from top to bottem. This is the basic init function, - * it can be used, extended or overwritten by the content items - * - * Its behaviour depends on the content item - * - * @package private - * - * @returns {void} - */ + /** + * The tree of content items is created in two steps: First all content items are instantiated, + * then init is called recursively from top to bottem. This is the basic init function, + * it can be used, extended or overwritten by the content items + * + * Its behaviour depends on the content item + * + * @package private + * + * @returns {void} + */ _$init: function () { var i; this.setSize(); @@ -3619,26 +3619,26 @@ this.emitBubblingEvent(this.type + 'Created'); }, - /** - * Emit an event that bubbles up the item tree. - * - * @param {String} name The name of the event - * - * @returns {void} - */ + /** + * Emit an event that bubbles up the item tree. + * + * @param {String} name The name of the event + * + * @returns {void} + */ emitBubblingEvent: function (name) { var event = new lm.utils.BubblingEvent(name, this); this.emit(name, event); }, - /** - * Private method, creates all content items for this node at initialisation time - * PLEASE NOTE, please see addChild for adding contentItems add runtime - * @private - * @param {configuration item node} config - * - * @returns {void} - */ + /** + * Private method, creates all content items for this node at initialisation time + * PLEASE NOTE, please see addChild for adding contentItems add runtime + * @private + * @param {configuration item node} config + * + * @returns {void} + */ _createContentItems: function (config) { var oContentItem, i; @@ -3652,13 +3652,13 @@ } }, - /** - * Extends an item configuration node with default settings - * @private - * @param {configuration item node} config - * - * @returns {configuration item node} extended config - */ + /** + * Extends an item configuration node with default settings + * @private + * @param {configuration item node} config + * + * @returns {configuration item node} extended config + */ _extendItemNode: function (config) { for (var key in lm.config.itemDefaultConfig) { @@ -3670,26 +3670,26 @@ return config; }, - /** - * Called for every event on the item tree. Decides whether the event is a bubbling - * event and propagates it to its parent - * - * @param {String} name the name of the event - * @param {lm.utils.BubblingEvent} event - * - * @returns {void} - */ + /** + * Called for every event on the item tree. Decides whether the event is a bubbling + * event and propagates it to its parent + * + * @param {String} name the name of the event + * @param {lm.utils.BubblingEvent} event + * + * @returns {void} + */ _propagateEvent: function (name, event) { if (event instanceof lm.utils.BubblingEvent && event.isPropagationStopped === false && this.isInitialised === true) { - /** - * In some cases (e.g. if an element is created from a DragSource) it - * doesn't have a parent and is not below root. If that's the case - * propagate the bubbling event from the top level of the substree directly - * to the layoutManager - */ + /** + * In some cases (e.g. if an element is created from a DragSource) it + * doesn't have a parent and is not below root. If that's the case + * propagate the bubbling event from the top level of the substree directly + * to the layoutManager + */ if (this.isRoot === false && this.parent) { this.parent.emit.apply(this.parent, Array.prototype.slice.call(arguments, 0)); } else { @@ -3698,16 +3698,16 @@ } }, - /** - * All raw events bubble up to the root element. Some events that - * are propagated to - and emitted by - the layoutManager however are - * only string-based, batched and sanitized to make them more usable - * - * @param {String} name the name of the event - * - * @private - * @returns {void} - */ + /** + * All raw events bubble up to the root element. Some events that + * are propagated to - and emitted by - the layoutManager however are + * only string-based, batched and sanitized to make them more usable + * + * @param {String} name the name of the event + * + * @private + * @returns {void} + */ _scheduleEventPropagationToLayoutManager: function (name, event) { if (lm.utils.indexOf(name, this._throttledEvents) === -1) { this.layoutManager.emit(name, event.origin); @@ -3720,25 +3720,25 @@ }, - /** - * Callback for events scheduled by _scheduleEventPropagationToLayoutManager - * - * @param {String} name the name of the event - * - * @private - * @returns {void} - */ + /** + * Callback for events scheduled by _scheduleEventPropagationToLayoutManager + * + * @param {String} name the name of the event + * + * @private + * @returns {void} + */ _propagateEventToLayoutManager: function (name, event) { this._pendingEventPropagations[name] = false; this.layoutManager.emit(name, event); } }); - /** - * @param {[type]} layoutManager [description] - * @param {[type]} config [description] - * @param {[type]} parent [description] - */ + /** + * @param {[type]} layoutManager [description] + * @param {[type]} config [description] + * @param {[type]} parent [description] + */ lm.items.Component = function (layoutManager, config, parent) { lm.items.AbstractContentItem.call(this, layoutManager, config, parent); @@ -3798,11 +3798,11 @@ lm.items.AbstractContentItem.prototype._$destroy.call(this); }, - /** - * Dragging onto a component directly is not an option - * - * @returns null - */ + /** + * Dragging onto a component directly is not an option + * + * @returns null + */ _$getArea: function () { return null; } @@ -3841,9 +3841,9 @@ this.element.width(width); this.element.height(height); - /* - * Root can be empty - */ + /* + * Root can be empty + */ if (this.contentItems[0]) { this.contentItems[0].element.width(width); this.contentItems[0].element.height(height); @@ -3917,18 +3917,18 @@ lm.utils.copy(lm.items.RowOrColumn.prototype, { - /** - * Add a new contentItem to the Row or Column - * - * @param {lm.item.AbstractContentItem} contentItem - * @param {[int]} index The position of the new item within the Row or Column. - * If no index is provided the item will be added to the end - * @param {[bool]} _$suspendResize If true the items won't be resized. This will leave the item in - * an inconsistent state and is only intended to be used if multiple - * children need to be added in one go and resize is called afterwards - * - * @returns {void} - */ + /** + * Add a new contentItem to the Row or Column + * + * @param {lm.item.AbstractContentItem} contentItem + * @param {[int]} index The position of the new item within the Row or Column. + * If no index is provided the item will be added to the end + * @param {[bool]} _$suspendResize If true the items won't be resized. This will leave the item in + * an inconsistent state and is only intended to be used if multiple + * children need to be added in one go and resize is called afterwards + * + * @returns {void} + */ addChild: function (contentItem, index, _$suspendResize) { var newItemSize, itemSize, i, splitterElement; @@ -3986,14 +3986,14 @@ }, - /** - * Removes a child of this element - * - * @param {lm.items.AbstractContentItem} contentItem - * @param {boolean} keepChild If true the child will be removed, but not destroyed - * - * @returns {void} - */ + /** + * Removes a child of this element + * + * @param {lm.items.AbstractContentItem} contentItem + * @param {boolean} keepChild If true the child will be removed, but not destroyed + * + * @returns {void} + */ removeChild: function (contentItem, keepChild) { var removedItemSize = contentItem.config[this._dimension], index = lm.utils.indexOf(contentItem, this.contentItems), @@ -4005,10 +4005,10 @@ throw new Error('Can\'t remove child. ContentItem is not child of this Row or Column'); } - /** - * Remove the splitter before the item or after if the item happens - * to be the first in the row/column - */ + /** + * Remove the splitter before the item or after if the item happens + * to be the first in the row/column + */ if (this._splitter[splitterIndex]) { this._splitter[splitterIndex]._$destroy(); this._splitter.splice(splitterIndex, 1); @@ -4019,9 +4019,9 @@ if (this.contentItems[i].config.fixed) fixedItemSize += this.contentItems[i].config[this._dimension]; } - /** - * Allocate the space that the removed item occupied to the remaining items - */ + /** + * Allocate the space that the removed item occupied to the remaining items + */ for (i = 0; i < this.contentItems.length; i++) { if (this.contentItems[i].config.fixed) ; @@ -4044,14 +4044,14 @@ } }, - /** - * Replaces a child of this Row or Column with another contentItem - * - * @param {lm.items.AbstractContentItem} oldChild - * @param {lm.items.AbstractContentItem} newChild - * - * @returns {void} - */ + /** + * Replaces a child of this Row or Column with another contentItem + * + * @param {lm.items.AbstractContentItem} oldChild + * @param {lm.items.AbstractContentItem} newChild + * + * @returns {void} + */ replaceChild: function (oldChild, newChild) { var size = oldChild.config[this._dimension]; lm.items.AbstractContentItem.prototype.replaceChild.call(this, oldChild, newChild); @@ -4060,11 +4060,11 @@ this.emitBubblingEvent('stateChanged'); }, - /** - * Called whenever the dimensions of this item or one of its parents change - * - * @returns {void} - */ + /** + * Called whenever the dimensions of this item or one of its parents change + * + * @returns {void} + */ setSize: function () { if (this.contentItems.length > 0) { this._calculateRelativeSizes(); @@ -4074,15 +4074,15 @@ this.emit('resize'); }, - /** - * Invoked recursively by the layout manager. AbstractContentItem.init appends - * the contentItem's DOM elements to the container, RowOrColumn init adds splitters - * in between them - * - * @package private - * @override AbstractContentItem._$init - * @returns {void} - */ + /** + * Invoked recursively by the layout manager. AbstractContentItem.init appends + * the contentItem's DOM elements to the container, RowOrColumn init adds splitters + * in between them + * + * @package private + * @override AbstractContentItem._$init + * @returns {void} + */ _$init: function () { if (this.isInitialised === true) return; @@ -4095,15 +4095,15 @@ } }, - /** - * Turns the relative sizes calculated by _calculateRelativeSizes into - * absolute pixel values and applies them to the children's DOM elements - * - * Assigns additional pixels to counteract Math.floor - * - * @private - * @returns {void} - */ + /** + * Turns the relative sizes calculated by _calculateRelativeSizes into + * absolute pixel values and applies them to the children's DOM elements + * + * Assigns additional pixels to counteract Math.floor + * + * @private + * @returns {void} + */ _setAbsoluteSizes: function () { var i, sizeData = this._calculateAbsoluteSizes(); @@ -4123,10 +4123,10 @@ } }, - /** - * Calculates the absolute sizes of all of the children of this Item. - * @returns {object} - Set with absolute sizes and additional pixels. - */ + /** + * Calculates the absolute sizes of all of the children of this Item. + * @returns {object} - Set with absolute sizes and additional pixels. + */ _calculateAbsoluteSizes: function () { var i, totalSplitterSize = (this.contentItems.length - 1) * this._splitterSize, @@ -4164,27 +4164,27 @@ }; }, - /** - * Calculates the relative sizes of all children of this Item. The logic - * is as follows: - * - * - Add up the total size of all items that have a configured size - * - * - If the total == 100 (check for floating point errors) - * Excellent, job done - * - * - If the total is > 100, - * set the size of items without set dimensions to 1/3 and add this to the total - * set the size off all items so that the total is hundred relative to their original size - * - * - If the total is < 100 - * If there are items without set dimensions, distribute the remainder to 100 evenly between them - * If there are no items without set dimensions, increase all items sizes relative to - * their original size so that they add up to 100 - * - * @private - * @returns {void} - */ + /** + * Calculates the relative sizes of all children of this Item. The logic + * is as follows: + * + * - Add up the total size of all items that have a configured size + * + * - If the total == 100 (check for floating point errors) + * Excellent, job done + * + * - If the total is > 100, + * set the size of items without set dimensions to 1/3 and add this to the total + * set the size off all items so that the total is hundred relative to their original size + * + * - If the total is < 100 + * If there are items without set dimensions, distribute the remainder to 100 evenly between them + * If there are no items without set dimensions, increase all items sizes relative to + * their original size so that they add up to 100 + * + * @private + * @returns {void} + */ _calculateRelativeSizes: function () { var i, @@ -4200,17 +4200,17 @@ } } - /** - * Everything adds up to hundred, all good :-) - */ + /** + * Everything adds up to hundred, all good :-) + */ if (Math.round(total) === 100) { this._respectMinItemWidth(); return; } - /** - * Allocate the remaining size to the items without a set dimension - */ + /** + * Allocate the remaining size to the items without a set dimension + */ if (Math.round(total) < 100 && itemsWithoutSetDimension.length > 0) { for (i = 0; i < itemsWithoutSetDimension.length; i++) { itemsWithoutSetDimension[i].config[dimension] = (100 - total) / itemsWithoutSetDimension.length; @@ -4219,12 +4219,12 @@ return; } - /** - * If the total is > 100, but there are also items without a set dimension left, assing 50 - * as their dimension and add it to the total - * - * This will be reset in the next step - */ + /** + * If the total is > 100, but there are also items without a set dimension left, assing 50 + * as their dimension and add it to the total + * + * This will be reset in the next step + */ if (Math.round(total) > 100) { for (i = 0; i < itemsWithoutSetDimension.length; i++) { itemsWithoutSetDimension[i].config[dimension] = 50; @@ -4232,9 +4232,9 @@ } } - /** - * Set every items size relative to 100 relative to its size to total - */ + /** + * Set every items size relative to 100 relative to its size to total + */ for (i = 0; i < this.contentItems.length; i++) { this.contentItems[i].config[dimension] = (this.contentItems[i].config[dimension] / total) * 100; } @@ -4242,10 +4242,10 @@ this._respectMinItemWidth(); }, - /** - * Adjusts the column widths to respect the dimensions minItemWidth if set. - * @returns {} - */ + /** + * Adjusts the column widths to respect the dimensions minItemWidth if set. + * @returns {} + */ _respectMinItemWidth: function () { var minItemWidth = this.layoutManager.config.dimensions ? (this.layoutManager.config.dimensions.minItemWidth || 0) : 0, sizeData = null, @@ -4266,9 +4266,9 @@ sizeData = this._calculateAbsoluteSizes(); - /** - * Figure out how much we are under the min item size total and how much room we have to use. - */ + /** + * Figure out how much we are under the min item size total and how much room we have to use. + */ for (var i = 0; i < this.contentItems.length; i++) { contentItem = this.contentItems[i]; @@ -4288,16 +4288,16 @@ allEntries.push(entry); } - /** - * If there is nothing under min, or there is not enough over to make up the difference, do nothing. - */ + /** + * If there is nothing under min, or there is not enough over to make up the difference, do nothing. + */ if (totalUnderMin === 0 || totalUnderMin > totalOverMin) { return; } - /** - * Evenly reduce all columns that are over the min item width to make up the difference. - */ + /** + * Evenly reduce all columns that are over the min item width to make up the difference. + */ reducePercent = totalUnderMin / totalOverMin; remainingWidth = totalUnderMin; for (i = 0; i < entriesOverMin.length; i++) { @@ -4307,31 +4307,31 @@ entry.width -= reducedWidth; } - /** - * Take anything remaining from the last item. - */ + /** + * Take anything remaining from the last item. + */ if (remainingWidth !== 0) { allEntries[allEntries.length - 1].width -= remainingWidth; } - /** - * Set every items size relative to 100 relative to its size to total - */ + /** + * Set every items size relative to 100 relative to its size to total + */ for (i = 0; i < this.contentItems.length; i++) { this.contentItems[i].config.width = (allEntries[i].width / sizeData.totalWidth) * 100; } }, - /** - * Instantiates a new lm.controls.Splitter, binds events to it and adds - * it to the array of splitters at the position specified as the index argument - * - * What it doesn't do though is append the splitter to the DOM - * - * @param {Int} index The position of the splitter - * - * @returns {lm.controls.Splitter} - */ + /** + * Instantiates a new lm.controls.Splitter, binds events to it and adds + * it to the array of splitters at the position specified as the index argument + * + * What it doesn't do though is append the splitter to the DOM + * + * @param {Int} index The position of the splitter + * + * @returns {lm.controls.Splitter} + */ _createSplitter: function (index) { var splitter; splitter = new lm.controls.Splitter(this._isColumn, this._splitterSize, this._splitterGrabSize); @@ -4342,16 +4342,16 @@ return splitter; }, - /** - * Locates the instance of lm.controls.Splitter in the array of - * registered splitters and returns a map containing the contentItem - * before and after the splitters, both of which are affected if the - * splitter is moved - * - * @param {lm.controls.Splitter} splitter - * - * @returns {Object} A map of contentItems that the splitter affects - */ + /** + * Locates the instance of lm.controls.Splitter in the array of + * registered splitters and returns a map containing the contentItem + * before and after the splitters, both of which are affected if the + * splitter is moved + * + * @param {lm.controls.Splitter} splitter + * + * @returns {Object} A map of contentItems that the splitter affects + */ _getItemsForSplitter: function (splitter) { var index = lm.utils.indexOf(splitter, this._splitter); @@ -4361,11 +4361,11 @@ }; }, - /** - * Gets the minimum dimensions for the given item configuration array - * @param item - * @private - */ + /** + * Gets the minimum dimensions for the given item configuration array + * @param item + * @private + */ _getMinimumDimensions: function (arr) { var minWidth = 0, minHeight = 0; @@ -4377,14 +4377,14 @@ return { horizontal: minWidth, vertical: minHeight }; }, - /** - * Invoked when a splitter's dragListener fires dragStart. Calculates the splitters - * movement area once (so that it doesn't need calculating on every mousemove event) - * - * @param {lm.controls.Splitter} splitter - * - * @returns {void} - */ + /** + * Invoked when a splitter's dragListener fires dragStart. Calculates the splitters + * movement area once (so that it doesn't need calculating on every mousemove event) + * + * @param {lm.controls.Splitter} splitter + * + * @returns {void} + */ _onSplitterDragStart: function (splitter) { var items = this._getItemsForSplitter(splitter), minSize = this.layoutManager.config.dimensions[this._isColumn ? 'minItemHeight' : 'minItemWidth']; @@ -4400,16 +4400,16 @@ this._splitterMaxPosition = items.after.element[this._dimension]() - (afterMinSize || minSize); }, - /** - * Invoked when a splitter's DragListener fires drag. Updates the splitters DOM position, - * but not the sizes of the elements the splitter controls in order to minimize resize events - * - * @param {lm.controls.Splitter} splitter - * @param {Int} offsetX Relative pixel values to the splitters original position. Can be negative - * @param {Int} offsetY Relative pixel values to the splitters original position. Can be negative - * - * @returns {void} - */ + /** + * Invoked when a splitter's DragListener fires drag. Updates the splitters DOM position, + * but not the sizes of the elements the splitter controls in order to minimize resize events + * + * @param {lm.controls.Splitter} splitter + * @param {Int} offsetX Relative pixel values to the splitters original position. Can be negative + * @param {Int} offsetY Relative pixel values to the splitters original position. Can be negative + * + * @returns {void} + */ _onSplitterDrag: function (splitter, offsetX, offsetY) { var offset = this._isColumn ? offsetY : offsetX; @@ -4419,15 +4419,15 @@ } }, - /** - * Invoked when a splitter's DragListener fires dragStop. Resets the splitters DOM position, - * and applies the new sizes to the elements before and after the splitter and their children - * on the next animation frame - * - * @param {lm.controls.Splitter} splitter - * - * @returns {void} - */ + /** + * Invoked when a splitter's DragListener fires dragStop. Resets the splitters DOM position, + * and applies the new sizes to the elements before and after the splitter and their children + * on the next animation frame + * + * @param {lm.controls.Splitter} splitter + * + * @returns {void} + */ _onSplitterDragStop: function (splitter) { var items = this._getItemsForSplitter(splitter), @@ -4451,7 +4451,7 @@ lm.items.Stack = function (layoutManager, config, parent) { lm.items.AbstractContentItem.call(this, layoutManager, config, parent); - this.element = $('
    '); + this.element = $('

    Click to create a new tab

    '); this._activeContentItem = null; var cfg = layoutManager.config; this._header = { // defaults' reconstruction from old configuration style @@ -4575,13 +4575,13 @@ this.emitBubblingEvent('stateChanged'); }, - /** - * Validates that the stack is still closable or not. If a stack is able - * to close, but has a non closable component added to it, the stack is no - * longer closable until all components are closable. - * - * @returns {void} - */ + /** + * Validates that the stack is still closable or not. If a stack is able + * to close, but has a non closable component added to it, the stack is no + * longer closable until all components are closable. + * + * @returns {void} + */ _$validateClosability: function () { var contentItem, isClosable, @@ -4607,51 +4607,51 @@ }, - /** - * Ok, this one is going to be the tricky one: The user has dropped {contentItem} onto this stack. - * - * It was dropped on either the stacks header or the top, right, bottom or left bit of the content area - * (which one of those is stored in this._dropSegment). Now, if the user has dropped on the header the case - * is relatively clear: We add the item to the existing stack... job done (might be good to have - * tab reordering at some point, but lets not sweat it right now) - * - * If the item was dropped on the content part things are a bit more complicated. If it was dropped on either the - * top or bottom region we need to create a new column and place the items accordingly. - * Unless, of course if the stack is already within a column... in which case we want - * to add the newly created item to the existing column... - * either prepend or append it, depending on wether its top or bottom. - * - * Same thing for rows and left / right drop segments... so in total there are 9 things that can potentially happen - * (left, top, right, bottom) * is child of the right parent (row, column) + header drop - * - * @param {lm.item} contentItem - * - * @returns {void} - */ + /** + * Ok, this one is going to be the tricky one: The user has dropped {contentItem} onto this stack. + * + * It was dropped on either the stacks header or the top, right, bottom or left bit of the content area + * (which one of those is stored in this._dropSegment). Now, if the user has dropped on the header the case + * is relatively clear: We add the item to the existing stack... job done (might be good to have + * tab reordering at some point, but lets not sweat it right now) + * + * If the item was dropped on the content part things are a bit more complicated. If it was dropped on either the + * top or bottom region we need to create a new column and place the items accordingly. + * Unless, of course if the stack is already within a column... in which case we want + * to add the newly created item to the existing column... + * either prepend or append it, depending on wether its top or bottom. + * + * Same thing for rows and left / right drop segments... so in total there are 9 things that can potentially happen + * (left, top, right, bottom) * is child of the right parent (row, column) + header drop + * + * @param {lm.item} contentItem + * + * @returns {void} + */ _$onDrop: function (contentItem) { - /* - * The item was dropped on the header area. Just add it as a child of this stack and - * get the hell out of this logic - */ + /* + * The item was dropped on the header area. Just add it as a child of this stack and + * get the hell out of this logic + */ if (this._dropSegment === 'header') { this._resetHeaderDropZone(); this.addChild(contentItem, this._dropIndex); return; } - /* - * The stack is empty. Let's just add the element. - */ + /* + * The stack is empty. Let's just add the element. + */ if (this._dropSegment === 'body') { this.addChild(contentItem); return; } - /* - * The item was dropped on the top-, left-, bottom- or right- part of the content. Let's - * aggregate some conditions to make the if statements later on more readable - */ + /* + * The item was dropped on the top-, left-, bottom- or right- part of the content. Let's + * aggregate some conditions to make the if statements later on more readable + */ var isVertical = this._dropSegment === 'top' || this._dropSegment === 'bottom', isHorizontal = this._dropSegment === 'left' || this._dropSegment === 'right', insertBefore = this._dropSegment === 'top' || this._dropSegment === 'left', @@ -4662,9 +4662,9 @@ stack, rowOrColumn; - /* - * The content item can be either a component or a stack. If it is a component, wrap it into a stack - */ + /* + * The content item can be either a component or a stack. If it is a component, wrap it into a stack + */ if (contentItem.isComponent) { stack = this.layoutManager.createContentItem({ type: 'stack', @@ -4675,20 +4675,20 @@ contentItem = stack; } - /* - * If the item is dropped on top or bottom of a column or left and right of a row, it's already - * layd out in the correct way. Just add it as a child - */ + /* + * If the item is dropped on top or bottom of a column or left and right of a row, it's already + * layd out in the correct way. Just add it as a child + */ if (hasCorrectParent) { index = lm.utils.indexOf(this, this.parent.contentItems); this.parent.addChild(contentItem, insertBefore ? index : index + 1, true); this.config[dimension] *= 0.5; contentItem.config[dimension] = this.config[dimension]; this.parent.callDownwards('setSize'); - /* - * This handles items that are dropped on top or bottom of a row or left / right of a column. We need - * to create the appropriate contentItem for them to live in - */ + /* + * This handles items that are dropped on top or bottom of a row or left / right of a column. We need + * to create the appropriate contentItem for them to live in + */ } else { type = isVertical ? 'column' : 'row'; rowOrColumn = this.layoutManager.createContentItem({ type: type }, this); @@ -4703,15 +4703,15 @@ } }, - /** - * If the user hovers above the header part of the stack, indicate drop positions for tabs. - * otherwise indicate which segment of the body the dragged item would be dropped on - * - * @param {Int} x Absolute Screen X - * @param {Int} y Absolute Screen Y - * - * @returns {void} - */ + /** + * If the user hovers above the header part of the stack, indicate drop positions for tabs. + * otherwise indicate which segment of the body the dragged item would be dropped on + * + * @param {Int} x Absolute Screen X + * @param {Int} y Absolute Screen Y + * + * @returns {void} + */ _$highlightDropZone: function (x, y) { var segment, area; @@ -4761,17 +4761,17 @@ } }; - /** - * If this Stack is a parent to rows, columns or other stacks only its - * header is a valid dropzone. - */ + /** + * If this Stack is a parent to rows, columns or other stacks only its + * header is a valid dropzone. + */ if (this._activeContentItem && this._activeContentItem.isComponent === false) { return headerArea; } - /** - * Highlight the entire body if the stack is empty - */ + /** + * Highlight the entire body if the stack is empty + */ if (this.contentItems.length === 0) { this._contentAreaDimensions.body = { @@ -4971,13 +4971,13 @@ lm.utils.BubblingEvent.prototype.stopPropagation = function () { this.isPropagationStopped = true; }; - /** - * Minifies and unminifies configs by replacing frequent keys - * and values with one letter substitutes. Config options must - * retain array position/index, add new options at the end. - * - * @constructor - */ + /** + * Minifies and unminifies configs by replacing frequent keys + * and values with one letter substitutes. Config options must + * retain array position/index, add new options at the end. + * + * @constructor + */ lm.utils.ConfigMinifier = function () { this._keys = [ 'settings', @@ -5031,160 +5031,160 @@ 'close', 'maximise', 'minimise', - 'open in new window' + 'new tab' ]; }; lm.utils.copy(lm.utils.ConfigMinifier.prototype, { - /** - * Takes a GoldenLayout configuration object and - * replaces its keys and values recursively with - * one letter counterparts - * - * @param {Object} config A GoldenLayout config object - * - * @returns {Object} minified config - */ + /** + * Takes a GoldenLayout configuration object and + * replaces its keys and values recursively with + * one letter counterparts + * + * @param {Object} config A GoldenLayout config object + * + * @returns {Object} minified config + */ minifyConfig: function (config) { var min = {}; this._nextLevel(config, min, '_min'); return min; }, - /** - * Takes a configuration Object that was previously minified - * using minifyConfig and returns its original version - * - * @param {Object} minifiedConfig - * - * @returns {Object} the original configuration - */ + /** + * Takes a configuration Object that was previously minified + * using minifyConfig and returns its original version + * + * @param {Object} minifiedConfig + * + * @returns {Object} the original configuration + */ unminifyConfig: function (minifiedConfig) { var orig = {}; this._nextLevel(minifiedConfig, orig, '_max'); return orig; }, - /** - * Recursive function, called for every level of the config structure - * - * @param {Array|Object} orig - * @param {Array|Object} min - * @param {String} translationFn - * - * @returns {void} - */ + /** + * Recursive function, called for every level of the config structure + * + * @param {Array|Object} orig + * @param {Array|Object} min + * @param {String} translationFn + * + * @returns {void} + */ _nextLevel: function (from, to, translationFn) { var key, minKey; for (key in from) { - /** - * For in returns array indices as keys, so let's cast them to numbers - */ + /** + * For in returns array indices as keys, so let's cast them to numbers + */ if (from instanceof Array) key = parseInt(key, 10); - /** - * In case something has extended Object prototypes - */ + /** + * In case something has extended Object prototypes + */ if (!from.hasOwnProperty(key)) continue; - /** - * Translate the key to a one letter substitute - */ + /** + * Translate the key to a one letter substitute + */ minKey = this[translationFn](key, this._keys); - /** - * For Arrays and Objects, create a new Array/Object - * on the minified object and recurse into it - */ + /** + * For Arrays and Objects, create a new Array/Object + * on the minified object and recurse into it + */ if (typeof from[key] === 'object') { to[minKey] = from[key] instanceof Array ? [] : {}; this._nextLevel(from[key], to[minKey], translationFn); - /** - * For primitive values (Strings, Numbers, Boolean etc.) - * minify the value - */ + /** + * For primitive values (Strings, Numbers, Boolean etc.) + * minify the value + */ } else { to[minKey] = this[translationFn](from[key], this._values); } } }, - /** - * Minifies value based on a dictionary - * - * @param {String|Boolean} value - * @param {Array} dictionary - * - * @returns {String} The minified version - */ + /** + * Minifies value based on a dictionary + * + * @param {String|Boolean} value + * @param {Array} dictionary + * + * @returns {String} The minified version + */ _min: function (value, dictionary) { - /** - * If a value actually is a single character, prefix it - * with ___ to avoid mistaking it for a minification code - */ + /** + * If a value actually is a single character, prefix it + * with ___ to avoid mistaking it for a minification code + */ if (typeof value === 'string' && value.length === 1) { return '___' + value; } var index = lm.utils.indexOf(value, dictionary); - /** - * value not found in the dictionary, return it unmodified - */ + /** + * value not found in the dictionary, return it unmodified + */ if (index === -1) { return value; - /** - * value found in dictionary, return its base36 counterpart - */ + /** + * value found in dictionary, return its base36 counterpart + */ } else { return index.toString(36); } }, _max: function (value, dictionary) { - /** - * value is a single character. Assume that it's a translation - * and return the original value from the dictionary - */ + /** + * value is a single character. Assume that it's a translation + * and return the original value from the dictionary + */ if (typeof value === 'string' && value.length === 1) { return dictionary[parseInt(value, 36)]; } - /** - * value originally was a single character and was prefixed with ___ - * to avoid mistaking it for a translation. Remove the prefix - * and return the original character - */ + /** + * value originally was a single character and was prefixed with ___ + * to avoid mistaking it for a translation. Remove the prefix + * and return the original character + */ if (typeof value === 'string' && value.substr(0, 3) === '___') { return value[3]; } - /** - * value was not minified - */ + /** + * value was not minified + */ return value; } }); - /** - * An EventEmitter singleton that propagates events - * across multiple windows. This is a little bit trickier since - * windows are allowed to open childWindows in their own right - * - * This means that we deal with a tree of windows. Hence the rules for event propagation are: - * - * - Propagate events from this layout to both parents and children - * - Propagate events from parent to this and children - * - Propagate events from children to the other children (but not the emitting one) and the parent - * - * @constructor - * - * @param {lm.LayoutManager} layoutManager - */ + /** + * An EventEmitter singleton that propagates events + * across multiple windows. This is a little bit trickier since + * windows are allowed to open childWindows in their own right + * + * This means that we deal with a tree of windows. Hence the rules for event propagation are: + * + * - Propagate events from this layout to both parents and children + * - Propagate events from parent to this and children + * - Propagate events from children to the other children (but not the emitting one) and the parent + * + * @constructor + * + * @param {lm.LayoutManager} layoutManager + */ lm.utils.EventHub = function (layoutManager) { lm.utils.EventEmitter.call(this); this._layoutManager = layoutManager; @@ -5195,15 +5195,15 @@ $(window).on('gl_child_event', this._boundOnEventFromChild); }; - /** - * Called on every event emitted on this eventHub, regardles of origin. - * - * @private - * - * @param {Mixed} - * - * @returns {void} - */ + /** + * Called on every event emitted on this eventHub, regardles of origin. + * + * @private + * + * @param {Mixed} + * + * @returns {void} + */ lm.utils.EventHub.prototype._onEventFromThis = function () { var args = Array.prototype.slice.call(arguments); @@ -5217,40 +5217,40 @@ this._childEventSource = null; }; - /** - * Called by the parent layout. - * - * @param {Array} args Event name + arguments - * - * @returns {void} - */ + /** + * Called by the parent layout. + * + * @param {Array} args Event name + arguments + * + * @returns {void} + */ lm.utils.EventHub.prototype._$onEventFromParent = function (args) { this._dontPropagateToParent = args[0]; this.emit.apply(this, args); }; - /** - * Callback for child events raised on the window - * - * @param {DOMEvent} event - * @private - * - * @returns {void} - */ + /** + * Callback for child events raised on the window + * + * @param {DOMEvent} event + * @private + * + * @returns {void} + */ lm.utils.EventHub.prototype._onEventFromChild = function (event) { this._childEventSource = event.originalEvent.__gl; this.emit.apply(this, event.originalEvent.__glArgs); }; - /** - * Propagates the event to the parent by emitting - * it on the parent's DOM window - * - * @param {Array} args Event name + arguments - * @private - * - * @returns {void} - */ + /** + * Propagates the event to the parent by emitting + * it on the parent's DOM window + * + * @param {Array} args Event name + arguments + * @private + * + * @returns {void} + */ lm.utils.EventHub.prototype._propagateToParent = function (args) { var event, eventName = 'gl_child_event'; @@ -5274,14 +5274,14 @@ } }; - /** - * Propagate events to children - * - * @param {Array} args Event name + arguments - * @private - * - * @returns {void} - */ + /** + * Propagate events to children + * + * @param {Array} args Event name + arguments + * @private + * + * @returns {void} + */ lm.utils.EventHub.prototype._propagateToChildren = function (args) { var childGl, i; @@ -5295,25 +5295,25 @@ }; - /** - * Destroys the EventHub - * - * @public - * @returns {void} - */ + /** + * Destroys the EventHub + * + * @public + * @returns {void} + */ lm.utils.EventHub.prototype.destroy = function () { $(window).off('gl_child_event', this._boundOnEventFromChild); }; - /** - * A specialised GoldenLayout component that binds GoldenLayout container - * lifecycle events to react components - * - * @constructor - * - * @param {lm.container.ItemContainer} container - * @param {Object} state state is not required for react components - */ + /** + * A specialised GoldenLayout component that binds GoldenLayout container + * lifecycle events to react components + * + * @constructor + * + * @param {lm.container.ItemContainer} container + * @param {Object} state state is not required for react components + */ lm.utils.ReactComponentHandler = function (container, state) { this._reactComponent = null; this._originalComponentWillUpdate = null; @@ -5326,15 +5326,15 @@ lm.utils.copy(lm.utils.ReactComponentHandler.prototype, { - /** - * Creates the react class and component and hydrates it with - * the initial state - if one is present - * - * By default, react's getInitialState will be used - * - * @private - * @returns {void} - */ + /** + * Creates the react class and component and hydrates it with + * the initial state - if one is present + * + * By default, react's getInitialState will be used + * + * @private + * @returns {void} + */ _render: function () { this._reactComponent = ReactDOM.render(this._getReactComponent(), this._container.getElement()[0]); this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function () { @@ -5345,36 +5345,36 @@ } }, - /** - * Removes the component from the DOM and thus invokes React's unmount lifecycle - * - * @private - * @returns {void} - */ + /** + * Removes the component from the DOM and thus invokes React's unmount lifecycle + * + * @private + * @returns {void} + */ _destroy: function () { ReactDOM.unmountComponentAtNode(this._container.getElement()[0]); this._container.off('open', this._render, this); this._container.off('destroy', this._destroy, this); }, - /** - * Hooks into React's state management and applies the componentstate - * to GoldenLayout - * - * @private - * @returns {void} - */ + /** + * Hooks into React's state management and applies the componentstate + * to GoldenLayout + * + * @private + * @returns {void} + */ _onUpdate: function (nextProps, nextState) { this._container.setState(nextState); this._originalComponentWillUpdate.call(this._reactComponent, nextProps, nextState); }, - /** - * Retrieves the react class from GoldenLayout's registry - * - * @private - * @returns {React.Class} - */ + /** + * Retrieves the react class from GoldenLayout's registry + * + * @private + * @returns {React.Class} + */ _getReactClass: function () { var componentName = this._container._config.component; var reactClass; @@ -5393,12 +5393,12 @@ return reactClass; }, - /** - * Copies and extends the properties array and returns the React element - * - * @private - * @returns {React.Element} - */ + /** + * Copies and extends the properties array and returns the React element + * + * @private + * @returns {React.Element} + */ _getReactComponent: function () { var defaultProps = { glEventHub: this._container.layoutManager.eventHub, diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 07ca0257c..08bcd55ae 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -63,6 +63,10 @@ .mainView-container { color: $dark-gray; + .lm_goldenlayout { + background: $medium-gray; + } + .lm_title { background: $light-gray; color: $dark-gray; @@ -153,7 +157,8 @@ cursor: auto; } -.mainView-innerContent, .mainView-innerContent-dark { +.mainView-innerContent, +.mainView-innerContent-dark { display: contents; flex-direction: row; position: relative; @@ -175,44 +180,52 @@ position: absolute; z-index: 2; background-color: $medium-gray; + .editable-title { background-color: $light-gray; } } } + .mainView-libraryHandle { background-color: $light-gray; } -.mainView-innerContent-dark -{ + +.mainView-innerContent-dark { .propertiesView { background-color: #252525; + input { background-color: $medium-gray; } - .propertiesView-sharingTable - { + + .propertiesView-sharingTable { background-color: $medium-gray; } + .editable-title { background-color: $medium-gray; } + .propertiesView-field { background-color: $medium-gray; } } + .mainView-propertiesDragger, .mainView-libraryHandle { background: #353535; } } + .mainView-container-dark { .contextMenu-cont { background: $medium-gray; color: $white; + input::placeholder { - color:$white; + color: $white; } } } @@ -432,6 +445,7 @@ right: unset !important; left: 0 !important; } + .lm_close_tab { padding: 0; width: 15px !important; @@ -443,7 +457,9 @@ right: unset !important; left: 0 !important; } -.lm_tab, .lm_tab_active { + +.lm_tab, +.lm_tab_active { display: flex !important; padding-right: 0 !important; } \ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index a054f0ae1..d1b8b2df0 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -55,6 +55,29 @@ display: inline; } +.empty-tabs-message { + position: absolute; + width: 100%; + z-index: 1; + top: 50%; + z-index: 1; + text-align: center; + font-size: 18; + color: $dark-gray; + + img { + position: relative; + top: -1px; + margin: 0 5px; + } +} + +.lm_header, +.lm_items { + z-index: 2; + position: relative; +} + .collectiondockingview-container { width: 100%; height: 100%; -- cgit v1.2.3-70-g09d2 From 02336134cff5f5789380bf5950864f4b95583cf6 Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Mon, 16 Aug 2021 16:09:13 -0400 Subject: commented column code --- .../collectionSchema/CollectionSchemaMovableColumn.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx index 456c38c68..2df95ffd8 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx @@ -21,27 +21,38 @@ export interface MovableColumnProps { ScreenToLocalTransform: () => Transform; } export class MovableColumn extends React.Component { + // The header of the column private _header?: React.RefObject = React.createRef(); + // The container of the function that is responsible for moving the column over to a new plac private _colDropDisposer?: DragManager.DragDropDisposer; + // initial column position private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 }; + // sensitivity to being dragged, in pixels private _sensitivity: number = 16; + // Column reference ID private _dragRef: React.RefObject = React.createRef(); onPointerEnter = (e: React.PointerEvent): void => { + // if the column is left-clicked and it is being dragged if (e.buttons === 1 && SnappingManager.GetIsDragging()) { this._header!.current!.className = "collectionSchema-col-wrapper"; document.addEventListener("pointermove", this.onDragMove, true); } } + onPointerLeave = (e: React.PointerEvent): void => { this._header!.current!.className = "collectionSchema-col-wrapper"; document.removeEventListener("pointermove", this.onDragMove, true); !e.buttons && document.removeEventListener("pointermove", this.onPointerMove); } + onDragMove = (e: PointerEvent): void => { + // only take into account the horizonal direction when a column is dragged const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); const rect = this._header!.current!.getBoundingClientRect(); + // Now store the point at the top center of the column when it was in its original position const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top); + // to be compared with its new horizontal position const before = x[0] < bounds[0]; this._header!.current!.className = "collectionSchema-col-wrapper"; if (before) this._header!.current!.className += " col-before"; @@ -58,11 +69,15 @@ export class MovableColumn extends React.Component { colDrop = (e: Event, de: DragManager.DropEvent) => { document.removeEventListener("pointermove", this.onDragMove, true); + // we only care about whether the column is shifted to the side const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); + // get the dimensions of the smallest rectangle that bounds the header const rect = this._header!.current!.getBoundingClientRect(); const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top); + // get whether the column was dragged before or after where it is now const before = x[0] < bounds[0]; const colDragData = de.complete.columnDragData; + // if there is colDragData, which happen when the drag is complete, reorder the columns according to the established variables if (colDragData) { e.stopPropagation(); this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns); @@ -85,8 +100,10 @@ export class MovableColumn extends React.Component { document.removeEventListener("pointermove", onRowMove); document.removeEventListener('pointerup', onRowUp); }; + // if the left mouse button is the one being held if (e.buttons === 1) { const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y); + // If the movemnt of the drag exceeds the sensitivity value if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) { document.removeEventListener("pointermove", this.onPointerMove); e.stopPropagation(); @@ -105,6 +122,7 @@ export class MovableColumn extends React.Component { onPointerDown = (e: React.PointerEvent, ref: React.RefObject) => { this._dragRef = ref; const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY); + // If the cell thing dragged is not being edited if (!(e.target as any)?.tagName.includes("INPUT")) { this._startDragPosition = { x: dx, y: dy }; document.addEventListener("pointermove", this.onPointerMove); -- cgit v1.2.3-70-g09d2 From 9031708067ee16d7b7e6b2689f45ee54ea5f1e4a Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Tue, 17 Aug 2021 14:14:26 -0400 Subject: Added empty tabs message todo: create buttons for recent tabs when all tabs in a dashboard are closed --- src/client/documents/Documents.ts | 2 +- src/client/goldenLayout.js | 8 +++++++- src/client/views/MainView.tsx | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 48886aa3b..47e0377df 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -269,7 +269,7 @@ export class DocumentOptions { linearViewIsExpanded?: boolean; // is linear view expanded useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox border?: string; //for searchbox - hoverBackgroundColor?: string; // background color of a label when hovered + hoverBackgroundColor?: string; // background color of a label when hovered } export namespace Docs { diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index db94cce3d..896237e1d 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -2355,6 +2355,7 @@ this.element.hide(); } }); + /** * This class represents a header above a Stack ContentItem. * @@ -2362,6 +2363,7 @@ * @param {lm.item.AbstractContentItem} parent */ lm.controls.Header = function (layoutManager, parent) { + lm.utils.EventEmitter.call(this); this.layoutManager = layoutManager; @@ -4449,7 +4451,11 @@ lm.items.Stack = function (layoutManager, config, parent) { lm.items.AbstractContentItem.call(this, layoutManager, config, parent); - this.element = $('

    Click to create a new tab

    '); + this.element = $( + '
    ' + + '

    Click to create a new tab

    ' + + '
    ' + ); this._activeContentItem = null; var cfg = layoutManager.config; this._header = { // defaults' reconstruction from old configuration style diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b0b8d7f41..ba8f2a4c8 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -448,6 +448,7 @@ export class MainView extends React.Component {
    ; } + expandFlyout = action((button: Doc) => { this._flyoutWidth = (this._flyoutWidth || 250); this._sidebarContent.proto = button.target as any; -- cgit v1.2.3-70-g09d2 From 53019659c2335906ac9e42d755548ea35dfc0365 Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Tue, 17 Aug 2021 15:05:09 -0400 Subject: Fixed linking relationship misattribution fixed bug where link relationships were stored as link descriptions --- src/client/views/linking/LinkEditor.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index f74b422d3..9d0938a6e 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -70,7 +70,7 @@ export class LinkEditor extends React.Component { } onDown = () => this.setDescripValue(this.description); - onRelationshipDown = () => this.setRelationshipValue(this.description); + onRelationshipDown = () => this.setRelationshipValue(this.relationship); @action handleChange = (e: React.ChangeEvent) => { this.description = e.target.value; } @@ -149,35 +149,35 @@ export class LinkEditor extends React.Component {
    this.changeFollowBehavior("default")}> Default -
    +
    this.changeFollowBehavior("add:left")}> Always open in new left pane -
    +
    this.changeFollowBehavior("add:right")}> Always open in new right pane -
    +
    this.changeFollowBehavior("replace:right")}> Always replace right tab -
    +
    this.changeFollowBehavior("replace:left")}> Always replace left tab -
    +
    this.changeFollowBehavior("fullScreen")}> Always open full screen -
    +
    this.changeFollowBehavior("add")}> Always open in a new tab -
    +
    this.changeFollowBehavior("replace")}> Replace Tab -
    + {this.props.linkDoc.linksToAnnotation ?
    this.changeFollowBehavior("openExternal")}> -- cgit v1.2.3-70-g09d2 From fe2fa471927060e8258ae08cc5b72b26661905e1 Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Wed, 25 Aug 2021 16:35:20 -0400 Subject: updated show/hide link icons --- src/client/views/linking/LinkMenuItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 53a7ae9ab..947755c31 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -165,12 +165,12 @@ export class LinkMenuItem extends React.Component {
    {this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}
    }>
    e.stopPropagation()}> -
    +
    {!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}
    }>
    e.stopPropagation()}> -
    +
    {!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}
    }> -- cgit v1.2.3-70-g09d2 From c5e96c72fcf149b9bcfe5f7f7a9c714de1d5fd9a Mon Sep 17 00:00:00 2001 From: 0x85FB9C51 <77808164+0x85FB9C51@users.noreply.github.com> Date: Wed, 25 Aug 2021 16:58:57 -0400 Subject: added comment, fixed path issue --- .../collectionSchema/CollectionSchemaCells.tsx | 239 +++++++++++---------- .../collectionSchema/CollectionSchemaHeaders.tsx | 2 +- 2 files changed, 126 insertions(+), 115 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index f7dfaaeb4..c8638dd12 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -26,7 +26,7 @@ import { SnappingManager } from "../../../util/SnappingManager"; import { undoBatch } from "../../../util/UndoManager"; import '../../../views/DocumentDecorations.scss'; import { EditableView } from "../../EditableView"; -import { MAX_ROW_HEIGHT } from '../../globalCssVariables.scss'; +import { MAX_ROW_HEIGHT } from '../../../../client/views/globalCssVariables.scss'; import { DocumentIconContainer } from "../../nodes/DocumentIcon"; import { OverlayView } from "../../OverlayView"; import "./CollectionSchemaView.scss"; @@ -38,27 +38,35 @@ export interface CellProps { row: number; col: number; rowProps: CellInfo; + // currently unused CollectionView: Opt; + // currently unused ContainingCollection: Opt; Document: Doc; + // column name fieldKey: string; + // currently unused renderDepth: number; + // called when a button is pressed on the node itself addDocTab: (document: Doc, where: string) => boolean; pinToPres: (document: Doc) => void; moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; isFocused: boolean; changeFocusedCellByIndex: (row: number, col: number) => void; + // set whether the cell is in the isEditing mode setIsEditing: (isEditing: boolean) => void; isEditable: boolean; setPreviewDoc: (doc: Doc) => void; setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean; getField: (row: number, col?: number) => void; + // currnetly unused showDoc: (doc: Doc | undefined, dataDoc?: any, screenX?: number, screenY?: number) => void; } @observer export class CollectionSchemaCell extends React.Component { + // return a field key that is corrected for whether it COMMENT public static resolvedFieldKey(column: string, rowDoc: Doc) { const fieldKey = column; if (fieldKey.startsWith("*")) { @@ -72,7 +80,9 @@ export class CollectionSchemaCell extends React.Component { @observable protected _isEditing: boolean = false; protected _focusRef = React.createRef(); protected _rowDoc = this.props.rowProps.original; + // Gets the serialized data in proto form of the base proto that this document's proto inherits from protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original); + // methods for dragging and dropping protected _dropDisposer?: DragManager.DragDropDisposer; @observable contents: string = ""; @@ -81,6 +91,7 @@ export class CollectionSchemaCell extends React.Component { @action onKeyDown = (e: KeyboardEvent): void => { + // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) { document.removeEventListener("keydown", this.onKeyDown); this._isEditing = true; @@ -90,7 +101,11 @@ export class CollectionSchemaCell extends React.Component { @action isEditingCallback = (isEditing: boolean): void => { + // a general method that takes a boolean that determines whether the cell should be in + // is-editing mode + // remove the event listener if it's there document.removeEventListener("keydown", this.onKeyDown); + // it's not already in is-editing mode, re-add the event listener isEditing && document.addEventListener("keydown", this.onKeyDown); this._isEditing = isEditing; this.props.setIsEditing(isEditing); @@ -99,12 +114,16 @@ export class CollectionSchemaCell extends React.Component { @action onPointerDown = async (e: React.PointerEvent): Promise => { + // pan to the cell this.onItemDown(e); + // focus on it this.props.changeFocusedCellByIndex(this.props.row, this.props.col); this.props.setPreviewDoc(this.props.rowProps.original); + // console.log("click cell"); let url: string; if (url = StrCast(this.props.rowProps.row.href)) { + // opens up the the doc in a new window, blurring the old one try { new URL(url); const temp = window.open(url)!; @@ -119,18 +138,25 @@ export class CollectionSchemaCell extends React.Component { @undoBatch applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => { + // apply a specified change to the cell const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) }); if (!res.success) return false; + // change what is rendered to this new changed cell content doc[this.renderFieldKey] = res.result; return true; + // return whether the change was successful } private drop = (e: Event, de: DragManager.DropEvent) => { + // if the drag has data at its completion if (de.complete.docDragData) { + // if only one doc was dragged if (de.complete.docDragData.draggedDocuments.length === 1) { + // update the renderFieldKey this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0]; } else { + // create schema document reflecting the new column arrangement const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {}); this._rowDataDoc[this.renderFieldKey] = coll; } @@ -139,7 +165,9 @@ export class CollectionSchemaCell extends React.Component { } protected dropRef = (ele: HTMLElement | null) => { + // if the drop disposer is not undefined, run its function this._dropDisposer?.(); + // if ele is not null, give ele a non-undefined drop disposer ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); } @@ -163,33 +191,46 @@ export class CollectionSchemaCell extends React.Component { return {contents ? contents?.valueOf() : "undefined"}; } - @computed get renderFieldKey() { return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original); } + @computed get renderFieldKey() { + // gets the resolved field key of this cell + return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original); + } + onItemDown = async (e: React.PointerEvent) => { + // if the document is a document used to change UI for search results in schema view if (this.props.Document._searchDoc) { const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); + // Jump to the this document DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext, undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc)); } } + renderCellWithType(type: string | undefined) { const dragRef: React.RefObject = React.createRef(); + // the column const fieldKey = this.renderFieldKey; + // the exact cell const field = this._rowDoc[fieldKey]; const onPointerEnter = (e: React.PointerEvent): void => { + // e.buttons === 1 means the left moue pointer is down if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) { dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; } }; const onPointerLeave = (e: React.PointerEvent): void => { + // change the class name to indicate that the cell is no longer being dragged dragRef.current!.className = "collectionSchemaView-cellContainer"; }; let contents = Field.toString(field as Field); + // display 2 hyphens instead of a blank box for empty cells contents = contents === "" ? "--" : contents; + // classname reflects the tatus of the cell let className = "collectionSchemaView-cellWrapper"; if (this._isEditing) className += " editing"; if (this.props.isFocused && this.props.isEditable) className += " focused"; @@ -197,72 +238,27 @@ export class CollectionSchemaCell extends React.Component { const positions = []; if (StrCast(this.props.Document._searchString).toLowerCase() !== "") { + // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents let term = (field instanceof Promise) ? "...promise pending..." : contents.toLowerCase(); const search = StrCast(this.props.Document._searchString).toLowerCase(); let start = term.indexOf(search); let tally = 0; + // if search is found in term if (start !== -1) { positions.push(start); } + // if search is found in term, continue finding all instances of search in term while (start < contents?.length && start !== -1) { term = term.slice(start + search.length + 1); tally += start + search.length + 1; start = term.indexOf(search); positions.push(tally + start); } + // remove the last position if (positions.length > 1) { positions.pop(); } } - - // handles input procedures for string cells - let stringInput = (value: string, retVal: boolean): void => { - let valueSansQuotes = value; - if (this._isEditing) { - const vsqLength = valueSansQuotes.length; - // get rid of outer quotes - valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0, - valueSansQuotes.charAt(vsqLength - 1) == "\"" ? vsqLength - 1 : vsqLength); - } - let inputAsString = '"'; - // escape any quotes in the string - for (const i of valueSansQuotes) { - if (i == '"') { - inputAsString += '\\"'; - } else { - inputAsString += i; - } - } - // add a closing quote - inputAsString += '"'; - //two options here: we can strip off outer quotes or we can figure out what's going on with the script - const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); - } - - // handles input procedure for number cells - let numberInput = (value: string, retVal: boolean): void => { - const inputscript = value.substring(value.startsWith("=") ? 1 : 0); - // if commas are not stripped, the parser only considers the numbers after the last comma - let inputSansCommas = ""; - for (let s of inputscript) { - if (!(s == ",")) { - inputSansCommas += s; - } - } - const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - const changeMade = value.length !== value.length || value.length - 2 !== value.length - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); - } - - // handles input procedure for boolean cells - let boolInput = (value: string, retVal: boolean): void => { - const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - const changeMade = value.length !== value.length || value.length - 2 !== value.length - script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); - } - const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined"; return (
    { } else { // check if the input is a number let inputIsNum = true; - for (let s of value) { - if (isNaN(parseInt(s)) && !(s == ".") && !(s == ",")) { + for (const s of value) { + if (isNaN(parseInt(s)) && !(s === ".") && !(s === ",")) { inputIsNum = false; } } - - let contentsAreNum = true; - for (let s of contents) { - if (isNaN(parseInt(s)) && !(s == ".") && !(s == ",")) { - contentsAreNum = false; - } - } // check if the input is a boolean - let inputIsBool: boolean = value == "false" || value == "true"; - let contentsAreBool: boolean = contents == "false" || contents == "true"; - - if (type == undefined) { - // what to do in the case - if (!inputIsNum && !inputIsBool && !value.startsWith("=")) { - // if it's not a number, it's a string, and should be processed as such - // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically - // after each edit - stringInput(value, retVal); - // handle numbers and expressions - } else if (inputIsNum || value.startsWith("=")) { - numberInput(value, retVal); - // handle booleans - } else if (inputIsBool) { - boolInput(value, retVal); + const inputIsBool: boolean = value === "false" || value === "true"; + // what to do in the case + if (!inputIsNum && !inputIsBool && !value.startsWith("=")) { + // if it's not a number, it's a string, and should be processed as such + // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically + // after each edit + let valueSansQuotes = value; + if (this._isEditing) { + const vsqLength = valueSansQuotes.length; + // get rid of outer quotes + valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0, + valueSansQuotes.charAt(vsqLength - 1) === "\"" ? vsqLength - 1 : vsqLength); } - // if the cell type is a string - } else if (type == "string") { - stringInput(value, retVal); - // if the cell type is a number - } else if (type == "number") { - if (inputIsNum) { - numberInput(value, retVal); - } else if (!contentsAreNum) { - stringInput("", retVal); + let inputAsString = '"'; + // escape any quotes in the string + for (const i of valueSansQuotes) { + if (i === '"') { + inputAsString += '\\"'; + } else { + inputAsString += i; + } } - // if the cell type is a boolean - } else if (type == "boolean") { - if (inputIsBool) { - boolInput(value, retVal); - } else if (!contentsAreBool) { - stringInput("", retVal); + // add a closing quote + inputAsString += '"'; + //two options here: we can strip off outer quotes or we can figure out what's going on with the script + const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length; + // change it if a change is made, otherwise, just compile using the old cell conetnts + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + // handle numbers and expressions + } else if (inputIsNum || value.startsWith("=")) { + //TODO: make accept numbers + const inputscript = value.substring(value.startsWith("=") ? 1 : 0); + // if commas are not stripped, the parser only considers the numbers after the last comma + let inputSansCommas = ""; + for (const s of inputscript) { + if (!(s === ",")) { + inputSansCommas += s; + } } + const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = value.length !== value.length || value.length - 2 !== value.length; + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); + // handle booleans + } else if (inputIsBool) { + const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + const changeMade = value.length !== value.length || value.length - 2 !== value.length; + script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run)); } } if (retVal) { @@ -351,6 +354,7 @@ export class CollectionSchemaCell extends React.Component { return retVal; })} OnFillDown={async (value: string) => { + // computes all of the value preceded by := const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); script.compiled && DocListCast(this.props.Document[this.props.fieldKey]). forEach((doc, i) => value.startsWith(":=") ? @@ -381,7 +385,10 @@ export class CollectionSchemaStringCell extends CollectionSchemaCell { render() @observer export class CollectionSchemaDateCell extends CollectionSchemaCell { - @computed get _date(): Opt { return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined; } + @computed get _date(): Opt { + // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined. + return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined; + } @action handleChange = (date: any) => { @@ -394,16 +401,13 @@ export class CollectionSchemaDateCell extends CollectionSchemaCell { //} } - // If the cell is not clicked on, render the date normally. Otherwise, render a date picker. render() { - return !this.props.isFocused ? {this._date ? Field.toString(this._date as Field) : "--"} : -
    - this.handleChange(date)} - onChange={date => this.handleChange(date)} - /> -
    + return !this.props.isFocused ? {this._date ? Field.toString(this._date as Field) : "--"} : + this.handleChange(date)} + onChange={date => this.handleChange(date)} + />; } } @@ -423,8 +427,9 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { typecheck: true, transformer: DocumentIconContainer.getTransformer() }); - + // compile the script const results = script.compiled && script.run(); + // if the script was compiled and run if (results && results.success) { this._rowDoc[this.renderFieldKey] = results.result; return true; @@ -442,6 +447,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { @action isEditingCallback = (isEditing: boolean): void => { + // the isEditingCallback from a general CollectionSchemaCell document.removeEventListener("keydown", this.onKeyDown); isEditing && document.addEventListener("keydown", this.onKeyDown); this._isEditing = isEditing; @@ -450,6 +456,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { } render() { + // if there's a doc, render it return !this._doc ? this.renderCellWithType("document") :
    Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; + // If there is a path, follow it; otherwise, follow a link to a default image icon const url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; - const aspect = Doc.NativeAspect(this._rowDoc); - let width = Math.min(75, this.props.rowProps.width); - const height = Math.min(75, width / aspect); - width = height * aspect; + const aspect = Doc.NativeAspect(this._rowDoc); // aspect ratio + let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px + const height = Math.min(75, width / aspect); // get a height either proportional to that or 75 px + width = height * aspect; // increase the width of the image if necessary to maintain proportionality const reference = React.createRef(); return
    @@ -523,13 +531,13 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { @computed get _field() { return this._rowDoc[this.renderFieldKey]; } @computed get _optionsList() { return this._field as List; } - @observable private _opened = false; + @observable private _opened = false; // whether the list is opened @observable private _text = "select an item"; - @observable private _selectedNum = 0; + @observable private _selectedNum = 0; // the index of the list item selected @action onSetValue = (value: string) => { - // change if its a document + // change if it's a document this._optionsList[this._selectedNum] = this._text = value; (this._field as List).splice(this._selectedNum, 1, value); @@ -537,6 +545,7 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { @action onSelected = (element: string, index: number) => { + // if an item is selected, the private variables should update to reflect this this._text = element; this._selectedNum = index; } @@ -550,6 +559,7 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { const link = false; const reference = React.createRef(); + // if the list is not opened, don't display it; otherwise, do. if (this._optionsList?.length) { const options = !this._opened ? (null) :
    @@ -617,6 +627,7 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { @observer export class CollectionSchemaButtons extends CollectionSchemaCell { + // the navigation buttons for schema view when it is used for search. render() { return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? <> :
    @@ -628,4 +639,4 @@ export class CollectionSchemaButtons extends CollectionSchemaCell {
    ; } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx index b2115b22e..2da9409f2 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx @@ -25,6 +25,7 @@ export interface AddColumnHeaderProps { @observer export class CollectionSchemaAddColumnHeader extends React.Component { + // the button that allows the user to add a column render() { return ( @@ -32,7 +33,6 @@ export class CollectionSchemaAddColumnHeader extends React.Component Date: Sat, 28 Aug 2021 16:58:02 -0400 Subject: added variable link weights based on relationship importance Link lines are thicker for links belonging to more important relationships. Thickness varies linearly from 3px to 12px. Removed dashed linked lines. --- src/client/documents/Documents.ts | 1 + src/client/util/LinkManager.ts | 2 ++ .../collectionFreeForm/CollectionFreeFormLinkView.tsx | 15 ++++++++++++--- src/client/views/linking/LinkEditor.tsx | 8 +++++++- src/fields/Doc.ts | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index db1e67203..08b2b8964 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -270,6 +270,7 @@ export class DocumentOptions { border?: string; //for searchbox hoverBackgroundColor?: string; // background color of a label when hovered linkRelationshipList?: List; // for storing different link relationships (when set by user in the link editor) + linkRelationshipSizes?: List; //stores number of links contained in each relationship linkColorList?: List; // colors of links corresponding to specific link relationships } export namespace Docs { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 84ff3a3ff..12ba57f45 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -105,8 +105,10 @@ export class LinkManager { if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) { const linkRelationshipList = new List(); const linkColorList = new List(); + const linkRelationshipSizes = new List(); Doc.UserDoc().linkRelationshipList = linkRelationshipList; Doc.UserDoc().linkColorList = linkColorList; + Doc.UserDoc().linkRelationshipSizes = linkRelationshipSizes; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 59891b7a1..92696138e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -41,7 +41,7 @@ export class CollectionFreeFormLinkView extends React.Component this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() + setTimeout(action(() => this._opacity = 0.75), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. const acont = A.rootDoc.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; const bcont = B.rootDoc.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; @@ -175,15 +175,24 @@ export class CollectionFreeFormLinkView extends React.Component; const linkColorList = Doc.UserDoc().linkColorList as List; + const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List; + const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); + //access stroke color using index of the relationship in the color list (default black) - const strokeColor = linkRelationshipList.indexOf(linkRelationship) == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; + const strokeColor = currRelationshipIndex == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; + + //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) + //thickness varies linearly from 3px to 12px for increasing link count + const strokeWidth: string = currRelationshipIndex == -1 ? "3px" : Math.floor(2 + 10 * (linkRelationshipSizes[currRelationshipIndex] / Math.max(...linkRelationshipSizes))) + "px"; + return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - {textX === undefined ? (null) : {StrCast(this.props.LinkDocs[0].description)} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index fba95cad2..f326e1440 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, StrListCast } from "../../../fields/Doc"; +import { Doc, NumListCast, StrListCast } from "../../../fields/Doc"; import { DateCast, StrCast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; @@ -43,12 +43,18 @@ export class LinkEditor extends React.Component { if (LinkManager.currentLink) { LinkManager.currentLink.linkRelationship = value; const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); + const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().linkColorList); + // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color if (linkRelationshipList && !linkRelationshipList.includes(value)) { linkRelationshipList.push(value); + linkRelationshipSizes.push(1); const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; linkColorList.push(randColor) + } else { + //increment relationship size if rel already exists + linkRelationshipSizes[linkRelationshipList.indexOf(value)] = linkRelationshipSizes[linkRelationshipList.indexOf(value)] + 1; } this.relationshipButtonColor = "rgb(62, 133, 55)"; setTimeout(action(() => this.relationshipButtonColor = ""), 750); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 17f41fac8..c57f31068 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -80,6 +80,7 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { export async function DocCastAsync(field: FieldResult): Promise> { return Cast(field, Doc); } +export function NumListCast(field: FieldResult) { return Cast(field, listSpec("number"), []); } export function StrListCast(field: FieldResult) { return Cast(field, listSpec("string"), []); } export function DocListCast(field: FieldResult) { return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; } export function DocListCastOrNull(field: FieldResult) { return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; } -- cgit v1.2.3-70-g09d2 From 2df1a0c0cc11c0dbcebf2a53e804cf8ac28db346 Mon Sep 17 00:00:00 2001 From: dinhanhtruong <70963346+dinhanhtruong@users.noreply.github.com> Date: Sun, 29 Aug 2021 13:47:03 -0400 Subject: fixed link counter when setting new relationships num of links per relationship should now update correctly when link relationships are set to new values --- src/client/views/linking/LinkEditor.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index f326e1440..ab8ce3da6 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -41,6 +41,7 @@ export class LinkEditor extends React.Component { @undoBatch setRelationshipValue = action((value: string) => { if (LinkManager.currentLink) { + const prevRelationship = LinkManager.currentLink.linkRelationship as string; LinkManager.currentLink.linkRelationship = value; const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); @@ -52,9 +53,15 @@ export class LinkEditor extends React.Component { linkRelationshipSizes.push(1); const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; linkColorList.push(randColor) - } else { - //increment relationship size if rel already exists + // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes + } else if (linkRelationshipList && value != prevRelationship) { + //increment size of new relationship size linkRelationshipSizes[linkRelationshipList.indexOf(value)] = linkRelationshipSizes[linkRelationshipList.indexOf(value)] + 1; + //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) + if (linkRelationshipList.includes(prevRelationship)) { + linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] = linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] - 1; + } + } this.relationshipButtonColor = "rgb(62, 133, 55)"; setTimeout(action(() => this.relationshipButtonColor = ""), 750); @@ -146,6 +153,7 @@ export class LinkEditor extends React.Component { style={{ width: "100%" }} id="input" value={this.relationship} + autoComplete={"off"} placeholder={"Enter link relationship"} onKeyDown={this.onRelationshipKey} onChange={this.handleRelationshipChange} @@ -174,6 +182,7 @@ export class LinkEditor extends React.Component {
    Date: Wed, 15 Sep 2021 14:41:24 -0400 Subject: fix earlier breaking changes for showing annotation bar and displaying context menu --- src/client/views/nodes/WebBox.tsx | 44 +++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 19135b6dd..fe5070fa4 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -69,10 +69,8 @@ export class WebBox extends ViewBoxAnnotatableComponent this._scrollHeight; @@ -391,20 +401,22 @@ export class WebBox extends ViewBoxAnnotatableComponent { + specificContextMenu = (e: React.MouseEvent | PointerEvent): void => { const cm = ContextMenu.Instance; const funcs: ContextMenuProps[] = []; - funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); - funcs.push({ - description: (!this.layoutDoc.allowReflow ? "Allow" : "Prevent") + " Reflow", event: () => { - const nw = !this.layoutDoc.allowReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); - this.layoutDoc.allowReflow = !nw; - if (nw) { - Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true); - } - }, icon: "snowflake" - }); - cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + if (!cm.findByDescription("Options...")) { + funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); + funcs.push({ + description: (!this.layoutDoc.allowReflow ? "Allow" : "Prevent") + " Reflow", event: () => { + const nw = !this.layoutDoc.allowReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); + this.layoutDoc.allowReflow = !nw; + if (nw) { + Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true); + } + }, icon: "snowflake" + }); + cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + } } @action -- cgit v1.2.3-70-g09d2 From 1395ce3fbf73bf5df5ed4add744c333cfc8008c9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 15 Sep 2021 15:43:24 -0400 Subject: another windows fix for contextmenus on web pages --- src/client/views/MainView.tsx | 1 + src/client/views/nodes/WebBox.tsx | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5bda9f6bf..d854f118f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -214,6 +214,7 @@ export class MainView extends React.Component { } } }, false); + document.oncontextmenu = () => false; } initAuthenticationRouters = async () => { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index fe5070fa4..cce71329d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -224,15 +224,20 @@ export class WebBox extends ViewBoxAnnotatableComponent - {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - ) - } -
    ; + return !this.inlineTextAnnotations.length ? (null) : +
    + {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => + ) + } +
    ; } @observable _showSidebar = false; -- cgit v1.2.3-70-g09d2 From 1643bfbbbe25fbd721d19ff2b77e795c6a2609d3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 Sep 2021 10:26:12 -0400 Subject: fixed unused code for embedding document references in text --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 5 +++-- src/client/views/nodes/formattedText/RichTextRules.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index caca215e5..3c2ff2df5 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -462,10 +462,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const target = dragData.droppedDocuments[0]; target._fitToBox = true; const node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), height: target[HeightSym](), + width: target[WidthSym](), + height: target[HeightSym](), title: "dashDoc", docid: target[Id], - float: "right" + float: "unset" }); const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 3fd7d61fa..711136469 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -282,7 +282,7 @@ export class RichTextRules { if (rstate) { this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); } - const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500, }, docid); + const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid); DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to", undefined); const fstate = this.TextBox.EditorView?.state; @@ -290,7 +290,7 @@ export class RichTextRules { this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); } }); - return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); } return state.tr; } -- cgit v1.2.3-70-g09d2 From 0ad7c7d6abe68ed5c9520c9b671f07013b8d86df Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 Sep 2021 17:47:18 -0400 Subject: made embedded dash docs in RTF resize when changed -- but they lose focus --- .../views/nodes/formattedText/DashDocView.tsx | 59 +++++++++++++--------- 1 file changed, 36 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 8915d7c47..e519de1c5 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -1,7 +1,7 @@ import { IReactionDisposer, reaction, observable, action } from "mobx"; import { NodeSelection } from "prosemirror-state"; import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc"; -import { Cast, StrCast } from "../../../../fields/Types"; +import { Cast, StrCast, NumCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; @@ -69,30 +69,31 @@ export class DashDocViewInternal extends React.Component { @observable _finalLayout: any; @observable _resolvedDataDoc: any; - constructor(props: IDashDocViewInternal) { - super(props); - this._textBox = this.props.tbox; - const updateDoc = action((dashDoc: Doc) => { - this._dashDoc = dashDoc; - this._finalLayout = this.props.docid ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc, this.props.fieldKey); + updateDoc = action((dashDoc: Doc) => { + this._dashDoc = dashDoc; + this._finalLayout = this.props.docid ? dashDoc : Doc.expandTemplateLayout(Doc.Layout(dashDoc), dashDoc, this.props.fieldKey); - if (this._finalLayout) { - if (!Doc.AreProtosEqual(this._finalLayout, dashDoc)) { - this._finalLayout.rootDocument = dashDoc.aliasOf; - } - this._resolvedDataDoc = Cast(this._finalLayout.resolvedDataDoc, Doc, null); + if (this._finalLayout) { + if (!Doc.AreProtosEqual(this._finalLayout, dashDoc)) { + this._finalLayout.rootDocument = dashDoc.aliasOf; } - if (this.props.width !== (this._dashDoc?._width ?? "") + "px" || this.props.height !== (this._dashDoc?._height ?? "") + "px") { - try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made - this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { - ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px" - })); - } catch (e) { - console.log("DashDocView:" + e); - } + this._resolvedDataDoc = Cast(this._finalLayout.resolvedDataDoc, Doc, null); + } + if (this.props.width !== (this._dashDoc?._width ?? "") + "px" || this.props.height !== (this._dashDoc?._height ?? "") + "px") { + try { // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made + this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { + ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px" + })); + } catch (e) { + console.log("DashDocView:" + e); } - }); + } + }); + + constructor(props: IDashDocViewInternal) { + super(props); + this._textBox = this.props.tbox; DocServer.GetRefField(this.props.docid + this.props.alias).then(async dashDoc => { if (!(dashDoc instanceof Doc)) { @@ -101,15 +102,27 @@ export class DashDocViewInternal extends React.Component { const aliasedDoc = Doc.MakeAlias(dashDocBase, this.props.docid + this.props.alias); aliasedDoc.layoutKey = "layout"; this.props.fieldKey && DocUtils.makeCustomViewClicked(aliasedDoc, Docs.Create.StackingDocument, this.props.fieldKey, undefined); - updateDoc(aliasedDoc); + this.updateDoc(aliasedDoc); } }); } else { - updateDoc(dashDoc); + this.updateDoc(dashDoc); } }); } + componentDidMount() { + this._disposers.upater = reaction(() => this._dashDoc && (NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width)), + () => { + if (this._dashDoc) { + this.props.view.dispatch(this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { + ...this.props.node.attrs, width: (this._dashDoc?._width ?? "") + "px", height: (this._dashDoc?._height ?? "") + "px" + })); + } + }); + } + + removeDoc = () => { this.props.view.dispatch(this.props.view.state.tr .setSelection(new NodeSelection(this.props.view.state.doc.resolve(this.props.getPos()))) -- cgit v1.2.3-70-g09d2 From 68b20c7cf3b3472a7c7adbdcffa2318ad3549d8d Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 Sep 2021 17:47:50 -0400 Subject: webbox search coming soon --- src/client/views/nodes/WebBox.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'src') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index cce71329d..0c9c36418 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -623,6 +623,25 @@ export class WebBox extends ViewBoxAnnotatableComponent
    + {/*
    + { + if (e.key === "Enter") { + (this._iframe?.contentWindow as any)?.find(e.target.value); + } + }} onChange={e => { + this._iframe?.contentWindow?.getSelection()?.empty(); + (this._iframe?.contentWindow as any)?.find(e.target.value) + }}> +
    */}
    ); } } -- cgit v1.2.3-70-g09d2 From 64119b5d8766725025b8b2bfda72f2401bba0f00 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 Sep 2021 21:04:57 -0400 Subject: added search() component method. changed search menu to call search on documents that match search string. added seach bar for web pages. --- src/client/views/GlobalKeyHandler.ts | 16 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/PDFBox.tsx | 18 ++++- src/client/views/nodes/WebBox.scss | 86 ++++++++++++++++++++++ src/client/views/nodes/WebBox.tsx | 65 +++++++++++----- src/client/views/pdf/PDFViewer.tsx | 13 +--- src/client/views/search/SearchBox.tsx | 15 ++-- 8 files changed, 172 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index f66c9c788..de6f4ae8b 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -10,10 +10,11 @@ import { Cast, PromiseValue } from "../../fields/Types"; import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager"; import { DocServer } from "../DocServer"; import { DocumentType } from "../documents/DocumentTypes"; -import { DictationManager } from "../util/DictationManager"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { DragManager } from "../util/DragManager"; import { GroupManager } from "../util/GroupManager"; import { SelectionManager } from "../util/SelectionManager"; +import { SettingsManager } from "../util/SettingsManager"; import { SharingManager } from "../util/SharingManager"; import { SnappingManager } from "../util/SnappingManager"; import { undoBatch, UndoManager } from "../util/UndoManager"; @@ -27,8 +28,6 @@ import { LightboxView } from "./LightboxView"; import { MainView } from "./MainView"; import { DocumentLinksButton } from "./nodes/DocumentLinksButton"; import { AnchorMenu } from "./pdf/AnchorMenu"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { SettingsManager } from "../util/SettingsManager"; const modifiers = ["control", "meta", "shift", "alt"]; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; @@ -222,10 +221,13 @@ export class KeyManager { PromiseValue(Cast(Doc.UserDoc()["tabs-button-tools"], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv })); break; case "f": - const searchBtn = Doc.UserDoc().searchBtn as Doc; - - if (searchBtn) { - MainView.Instance.selectMenu(searchBtn); + if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) { + SelectionManager.Views()[0].ComponentView?.search?.("", false, false); + } else { + const searchBtn = Doc.UserDoc().searchBtn as Doc; + if (searchBtn) { + MainView.Instance.selectMenu(searchBtn); + } } break; case "o": diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index be0b078ec..94cf1c5a6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -902,7 +902,9 @@ export class CollectionFreeFormView extends CollectionSubView string; getScrollHeight?: () => number; + search?: (str:string, bwd?:boolean, clear?:boolean) => boolean; } export interface DocumentViewSharedProps { renderDepth: number; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index ce851b830..274d166f1 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -96,7 +96,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent this._pdfViewer?.search(string, fwd); + public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { + if (!this._searching && !clear) { + this._searching = true; + setTimeout(() => { + this._searchRef.current?.focus(); + this._searchRef.current?.select(); + this._searchRef.current?.setRangeText(searchString); + }); + } + return this._pdfViewer?.search(searchString, bwd, clear) || false; + }); public prevAnnotation = () => this._pdfViewer?.prevAnnotation(); public nextAnnotation = () => this._pdfViewer?.nextAnnotation(); public backPage = () => { this.Document._curPage = (this.Document._curPage || 1) - 1; return true; }; @@ -184,8 +194,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
    +
    + + ; + } + searchStringChanged = (e: React.ChangeEvent) => this._searchString = e.currentTarget.value; showInfo = action((anno: Opt) => this._overlayAnnoInfo = anno); setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func; panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); @@ -623,25 +666,7 @@ export class WebBox extends ViewBoxAnnotatableComponent - {/*
    - { - if (e.key === "Enter") { - (this._iframe?.contentWindow as any)?.find(e.target.value); - } - }} onChange={e => { - this._iframe?.contentWindow?.getSelection()?.empty(); - (this._iframe?.contentWindow as any)?.find(e.target.value) - }}> -
    */} + {!this.props.isContentActive() ? (null) : this.searchUI} ); } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index d953c6b6c..7aa18e46f 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -125,12 +125,6 @@ export class PDFViewer extends React.Component { } }); - this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.props.rootDoc), - m => { - if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), m.searchMatch > 0); - else !(this._lastSearch = false) && setTimeout(() => !this._lastSearch && this.search("", false, true), 200); - }, { fireImmediately: true }); - this._disposers.selected = reaction(() => this.props.isSelected(), selected => { // if (!selected) { @@ -337,10 +331,10 @@ export class PDFViewer extends React.Component { } @action - search = (searchString: string, fwd: boolean, clear: boolean = false) => { + search = (searchString: string, bwd?: boolean, clear: boolean = false) => { const findOpts = { caseSensitive: false, - findPrevious: !fwd, + findPrevious: bwd, highlightAll: true, phraseSearch: true, query: searchString @@ -348,7 +342,7 @@ export class PDFViewer extends React.Component { if (clear) { this._pdfViewer?.findController.executeCommand('reset', { query: "" }); } else if (!searchString) { - fwd ? this.nextAnnotation() : this.prevAnnotation(); + bwd ? this.prevAnnotation() : this.nextAnnotation(); } else if (this._pdfViewer?.pageViewsReady) { this._pdfViewer.findController.executeCommand('findagain', findOpts); } @@ -357,6 +351,7 @@ export class PDFViewer extends React.Component { this._mainCont.current.addEventListener("pagesloaded", executeFind); this._mainCont.current.addEventListener("pagerendered", executeFind); } + return true; } @action diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 9c353e9d0..3612bd7c4 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -104,9 +104,9 @@ export class SearchBox extends ViewBoxBaseComponent { - this.selectElement(doc); + onResultClick = action(async (doc: Doc) => { this._selectedResult = doc; + this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, false)); }); makeLink = action((linkTo: Doc) => { @@ -269,8 +269,8 @@ export class SearchBox extends ViewBoxBaseComponent { - await DocumentManager.Instance.jumpToDocument(doc, true); + selectElement = async (doc: Doc, finishFunc: () => void) => { + await DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, undefined, undefined, finishFunc); } /** @@ -307,7 +307,12 @@ export class SearchBox extends ViewBoxBaseComponent
    {title}
    }> -
    this.makeLink(result[0]) : () => this.onResultClick(result[0])} className={className}> +
    this.makeLink(result[0]) : + e => { + this.onResultClick(result[0]); + e.stopPropagation(); + }} className={className}>
    {title}
    -- cgit v1.2.3-70-g09d2 From 03c0caa35fa4ac63ac3efeb73de145ebd848cc54 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 Sep 2021 21:07:46 -0400 Subject: from last --- src/client/views/nodes/PDFBox.tsx | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 274d166f1..f0a502e31 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -117,11 +117,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent { let processed = false; switch (e.key) { - case "f": if (e.ctrlKey) { - setTimeout(() => this._searchRef.current?.focus(), 100); - this._searching = processed = true; - } - break; case "PageDown": processed = this.forwardPage(); break; case "PageUp": processed = this.backPage(); break; } -- cgit v1.2.3-70-g09d2 From e5e046fbf76dad34ef59754b9ed4c2469e5b849f Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 00:17:15 -0400 Subject: fixed being able to create inline (text selection) annotations on web pages --- src/client/views/nodes/WebBox.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index cb9256595..96a3ce83b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -539,12 +539,10 @@ export class WebBox extends ViewBoxAnnotatableComponent - {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - ) - } -
    ; + return
    + {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => + )} +
    ; } @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } -- cgit v1.2.3-70-g09d2 From 1de71a39f1f6ebc2c909c92694db340cc9b256a0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 00:28:44 -0400 Subject: fixed keeping anchor menu visible when selecting a new marker color from menu --- src/client/views/nodes/WebBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 96a3ce83b..acccccfb4 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -224,7 +224,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { -- cgit v1.2.3-70-g09d2 From f5c4aa829955467c37ff35fb47b6d3c47fef4590 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 02:14:31 -0400 Subject: fixed right-drag marquee on webBox on Mac --- src/client/views/ContextMenu.tsx | 20 +++++++++++--------- src/client/views/MarqueeAnnotator.tsx | 6 +++--- src/client/views/nodes/WebBox.tsx | 26 ++++++++++++-------------- 3 files changed, 26 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 78564a11b..80ff16cf9 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -26,8 +26,7 @@ export class ContextMenu extends React.Component { @observable private _mouseX: number = -1; @observable private _mouseY: number = -1; @observable private _shouldDisplay: boolean = false; - @observable private _mouseDown: boolean = false; - + private _ignoreUp = false; private _reactionDisposer?: IReactionDisposer; constructor(props: Readonly<{}>) { @@ -36,17 +35,23 @@ export class ContextMenu extends React.Component { ContextMenu.Instance = this; } + public setIgnoreEvents(ignore: boolean) { + this._ignoreUp = ignore; + } + @action onPointerDown = (e: PointerEvent) => { - this._mouseDown = true; this._mouseX = e.clientX; this._mouseY = e.clientY; } @action onPointerUp = (e: PointerEvent) => { - this._mouseDown = false; const curX = e.clientX; const curY = e.clientY; + if (this._ignoreUp) { + this._ignoreUp = false; + return; + } if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) { this._shouldDisplay = false; } @@ -62,7 +67,7 @@ export class ContextMenu extends React.Component { componentWillUnmount() { document.removeEventListener("pointerdown", this.onPointerDown); document.removeEventListener("pointerup", this.onPointerUp); - this._reactionDisposer && this._reactionDisposer(); + this._reactionDisposer?.(); } @action @@ -70,10 +75,6 @@ export class ContextMenu extends React.Component { document.addEventListener("pointerdown", this.onPointerDown); document.addEventListener("pointerup", this.onPointerUp); - this._reactionDisposer = reaction( - () => this._shouldDisplay, - () => this._shouldDisplay && !this._mouseDown && runInAction(() => this._display = true) - ); } @action @@ -156,6 +157,7 @@ export class ContextMenu extends React.Component { this._searchString = initSearch; this._shouldDisplay = true; this._onDisplay = onDisplay; + this._display = !onDisplay; } @action diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 26e76090a..563261dec 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -31,7 +31,7 @@ export interface MarqueeAnnotatorProps { annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; getPageFromScroll?: (top: number) => number; - finishMarquee: (x?: number, y?: number) => void; + finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); } @observer @@ -222,10 +222,10 @@ export class MarqueeAnnotator extends React.Component { if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up this.highlight("rgba(245, 230, 95, 0.75)", false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) } - this.props.finishMarquee(); + this.props.finishMarquee(undefined, undefined, e); } else { runInAction(() => this._width = this._height = 0); - this.props.finishMarquee(cliX, cliY); + this.props.finishMarquee(cliX, cliY, e); } } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index acccccfb4..9dafaadcb 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -247,20 +247,10 @@ export class WebBox extends ViewBoxAnnotatableComponent MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false); } } - @action finishMarquee = (x?: number, y?: number) => { + @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => { this._marqueeing = undefined; this._isAnnotating = false; this._iframeClick = undefined; - x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false); + if (x !== undefined && y !== undefined) { + this._setPreviewCursor?.(x, y, false, false); + ContextMenu.Instance.closeMenu(); + ContextMenu.Instance.setIgnoreEvents(false); + if (e?.button === 2 || e?.ctrlKey) { + this.specificContextMenu(undefined as any); + this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y); + } + } } @computed get urlContent() { -- cgit v1.2.3-70-g09d2 From a5a4cc47944766a29e8b6dc2364c35de52c1400d Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 02:40:40 -0400 Subject: fixed top-down resizing of web pages and pdfs. turned off Cors option for novices. --- src/client/views/DocumentDecorations.tsx | 9 +++++---- src/client/views/nodes/WebBox.tsx | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 6f9697703..3d6f157b6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -280,6 +280,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P case "documentDecorations-topResizer": dY = -1; dH = -move[1]; + dragBottom = true; break; case "documentDecorations-bottomLeftResizer": dX = -1; @@ -387,10 +388,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] })) .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => { Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth) - ({ - X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width, - Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height - }))); + ({ + X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width, + Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height + }))); Doc.SetNativeWidth(doc, undefined); Doc.SetNativeHeight(doc, undefined); }); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 9dafaadcb..f230550e4 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -423,7 +423,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); + !Doc.UserDoc().noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); funcs.push({ description: (!this.layoutDoc.allowReflow ? "Allow" : "Prevent") + " Reflow", event: () => { const nw = !this.layoutDoc.allowReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); -- cgit v1.2.3-70-g09d2 From 582c709a2b08e2f6c12793121d0c8b264ddc2ca2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 03:28:19 -0400 Subject: close context menu when starting a right-click drag on a webbox. .fixed dismissing marquee menu from outer view when clicking on nested webbox. AltKey drags marquee, not CtrlKey. --- .../views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index cedeb1112..ecff01e67 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -16,7 +16,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu { public showMarquee: () => void = unimplementedFunction; public hideMarquee: () => void = unimplementedFunction; public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; - + public isShown = () => { return this._opacity > 0; } constructor(props: Readonly<{}>) { super(props); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index f230550e4..ca1b0d8ac 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -35,6 +35,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from "./LinkDocPreview"; import "./WebBox.scss"; import React = require("react"); +import { MarqueeOptionsMenu } from "../collections/collectionFreeForm"; const _global = (window /* browser */ || global /* node */) as any; const htmlToText = require("html-to-text"); @@ -247,9 +248,10 @@ export class WebBox extends ViewBoxAnnotatableComponent; return ( -
    +
    Date: Fri, 17 Sep 2021 04:13:34 -0400 Subject: fixed not inadvertently moving webBoxes after creating a marquee adn then clicking in the webBox, then clicking outside it. --- src/client/views/AntimodeMenu.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 ++++++-- src/client/views/nodes/WebBox.tsx | 7 ++++--- 3 files changed, 11 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index fe6d39ca4..0f1fc6b69 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -16,7 +16,7 @@ export abstract class AntimodeMenu extends React.Co @observable protected _top: number = -300; @observable protected _left: number = -300; - @observable protected _opacity: number = 1; + @observable protected _opacity: number = 0; @observable protected _transitionProperty: string = "opacity"; @observable protected _transitionDuration: string = "0.5s"; @observable protected _transitionDelay: string = ""; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 187905960..46a8b6629 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -96,7 +96,7 @@ export interface DocComponentView { annotationKey?: string; getTitle?: () => string; getScrollHeight?: () => number; - search?: (str:string, bwd?:boolean, clear?:boolean) => boolean; + search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; } export interface DocumentViewSharedProps { renderDepth: number; @@ -556,10 +556,14 @@ export class DocumentViewInternal extends DocComponent { + cleanupPointerEvents = () => { this.cleanUpInteractions(); document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); + } + + onPointerUp = (e: PointerEvent): void => { + this.cleanupPointerEvents(); if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ca1b0d8ac..6a20ca14a 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -152,6 +152,7 @@ export class WebBox extends ViewBoxAnnotatableComponent disposer?.()); this._iframe?.removeEventListener('wheel', this.iframeWheel, true); + this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp); } @action @@ -215,8 +216,8 @@ export class WebBox extends ViewBoxAnnotatableComponent { + this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here. if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) { - this._iframe.contentDocument.addEventListener("pointerup", this.iframeUp); const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!); const scale = (this.props.scaling?.() || 1) * mainContBounds.scale; const sel = this._iframe.contentWindow.getSelection(); @@ -237,7 +238,6 @@ export class WebBox extends ViewBoxAnnotatableComponent this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. } else { this._iframeClick = this._iframe ?? undefined; @@ -267,6 +267,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { const iframe = this._iframe; + this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp); if (iframe?.contentDocument) { iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown); this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight); @@ -457,7 +458,7 @@ export class WebBox extends ViewBoxAnnotatableComponent Date: Fri, 17 Sep 2021 14:07:09 -0400 Subject: added 'unset' docFilters. changed link doc views to use comparison box with title/caption. fixed linkEditor to write to data doc. generalized comparisonBox rendering to use parameterized fields. fixed pdf/web to honor pointerEvents none prop and fixed textAnnotations to get rendered once as an Annotation. moved filterIcon stuff into DocumentView --- src/Utils.ts | 4 +++- src/client/DocServer.ts | 2 ++ src/client/documents/Documents.ts | 17 +++++++++++------ src/client/views/collections/CollectionView.tsx | 12 ------------ .../collectionFreeForm/CollectionFreeFormLinkView.tsx | 6 +++--- src/client/views/linking/LinkEditor.tsx | 11 ++++++----- src/client/views/nodes/ComparisonBox.tsx | 8 +++++--- src/client/views/nodes/DocumentView.tsx | 19 +++++++++++++++++-- src/client/views/nodes/LinkBox.tsx | 19 ++++++------------- src/client/views/nodes/LinkDescriptionPopup.tsx | 7 ++++--- src/client/views/nodes/PDFBox.tsx | 9 ++++----- src/client/views/nodes/WebBox.tsx | 14 ++++++++------ src/client/views/pdf/Annotation.scss | 3 +++ src/client/views/pdf/PDFViewer.tsx | 13 +++++++------ src/fields/Doc.ts | 2 +- 15 files changed, 80 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 6eacd8296..e11f1154e 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -125,7 +125,9 @@ export namespace Utils { // bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:x`;// bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field } - + export function PropUnsetFilter(prop: string) { + return `${prop}:any,${noRecursionHack}:unset`; + } export function toRGBAstr(col: { r: number, g: number, b: number, a?: number }) { return "rgba(" + col.r + "," + col.g + "," + col.b + (col.a !== undefined ? "," + col.a : "") + ")"; diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index e498a7cca..3b376a0e7 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -241,6 +241,7 @@ export namespace DocServer { // the field has been returned from the server const getSerializedField = Utils.EmitCallback(_socket, MessageStore.GetRefField, id); + console.log(id) // when the serialized RefField has been received, go head and begin deserializing it into an object. // Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. @@ -264,6 +265,7 @@ export namespace DocServer { } else { delete _cache[id]; } + console.log(id, field); return field; // either way, overwrite or delete any promises cached at this id (that we inserted as flags // to indicate that the field was in the process of being fetched). Now everything diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f50f306a3..e8185400e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -962,13 +962,17 @@ export namespace DocUtils { // metadata facets that exist const exists = Object.keys(facet).filter(value => facet[value] === "exists"); + // metadata facets that exist + const unsets = Object.keys(facet).filter(value => facet[value] === "unset"); + // facets that have an x next to them const xs = Object.keys(facet).filter(value => facet[value] === "x"); - if (!exists.length && !xs.length && !checks.length && !matches.length) return true; + if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value)); const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value)); const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined); + const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined); const satisfiesMatchFacets = !matches.length ? true : matches.some(value => { if (facetKey.startsWith("*")) { // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc const allKeys = Array.from(Object.keys(d)); @@ -980,11 +984,11 @@ export namespace DocUtils { }); // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria if ((parentCollection?.currentFilter as Doc)?.filterBoolean === "OR") { - if (satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; + if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; } // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria else { - if (!satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; + if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; } } @@ -1088,10 +1092,11 @@ export namespace DocUtils { "anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, - layout_linkView: Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null), - linkDisplay: true, hidden: true, + linkDisplay: true, + hidden: true, linkRelationship, - _layoutKey: "layout_linkView", + _showCaption: "description", + _showTitle: "linkRelationship", description }, id), showPopup); } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 5c9c8063b..38e027fb3 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -246,12 +246,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); } - /** - * Shows the filter icon if it's a user-created collection which isn't a dashboard and has some docFilters applied on it or on the current dashboard. - */ - @computed get showFilterIcon() { - return this.props.Document.viewType !== CollectionViewType.Docking && !Doc.IsSystem(this.props.Document) && this._subView?.IsFiltered(); - } @observable _subView: any = undefined; @@ -280,12 +274,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent {this.showIsTagged()} {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} - {this.showFilterIcon ? - { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })} - /> - : (null)}
    ); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 3b3e069d8..2cb487588 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,6 +1,6 @@ import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../../fields/Doc"; +import { Doc, Field } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { List } from "../../../../fields/List"; import { NumCast, StrCast } from "../../../../fields/Types"; @@ -180,7 +180,7 @@ export class CollectionFreeFormLinkView extends React.Component; const linkColorList = Doc.UserDoc().linkColorList as List; //access stroke color using index of the relationship in the color list (default black) @@ -189,7 +189,7 @@ export class CollectionFreeFormLinkView extends React.Component {textX === undefined ? (null) : - {StrCast(this.props.LinkDocs[0].description)} + {Field.toString(this.props.LinkDocs[0].description as any as Field)} } ); } diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 240a71c3e..219f7d3a2 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -2,13 +2,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, StrListCast } from "../../../fields/Doc"; -import { DateCast, StrCast } from "../../../fields/Types"; +import { Doc, StrListCast, Field } from "../../../fields/Doc"; +import { DateCast, StrCast, Cast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; import './LinkEditor.scss'; import { LinkRelationshipSearch } from "./LinkRelationshipSearch"; import React = require("react"); +import { ToString } from "../../../fields/FieldSymbols"; interface LinkEditorProps { @@ -20,7 +21,7 @@ interface LinkEditorProps { @observer export class LinkEditor extends React.Component { - @observable description = StrCast(LinkManager.currentLink?.description); + @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); @observable openDropdown: boolean = false; @observable showInfo: boolean = false; @@ -41,7 +42,7 @@ export class LinkEditor extends React.Component { @undoBatch setRelationshipValue = action((value: string) => { if (LinkManager.currentLink) { - LinkManager.currentLink.linkRelationship = value; + Doc.GetProto(LinkManager.currentLink).linkRelationship = value; const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); const linkColorList = StrListCast(Doc.UserDoc().linkColorList); // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color @@ -79,7 +80,7 @@ export class LinkEditor extends React.Component { @undoBatch setDescripValue = action((value: string) => { if (LinkManager.currentLink) { - LinkManager.currentLink.description = value; + Doc.GetProto(LinkManager.currentLink).description = value; this.buttonColor = "rgb(62, 133, 55)"; setTimeout(action(() => this.buttonColor = ""), 750); return true; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 6708a08ee..72c7e4f45 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -15,6 +15,7 @@ import "./ComparisonBox.scss"; import { DocumentView, DocumentViewProps } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import React = require("react"); +import { DocumentType } from '../../documents/DocumentTypes'; export const comparisonSchema = createSchema({}); @@ -87,7 +88,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent; }; const displayDoc = (which: string) => { - const whichDoc = Cast(this.dataDoc[`compareBox-${which}`], Doc, null); + var whichDoc = Cast(this.dataDoc[which], Doc, null); + if (whichDoc.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null); return whichDoc ? <> - {displayBox("after", 1, this.props.PanelWidth() - 3)} + {displayBox(this.fieldKey === "data" ? "compareBox-after" : `${this.fieldKey}2`, 1, this.props.PanelWidth() - 3)}
    - {displayBox("before", 0, 0)} + {displayBox(this.fieldKey === "data" ? "compareBox-before" : `${this.fieldKey}1`, 0, 0)}
    (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? "w-resize" : undefined }} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 46a8b6629..35249c7f4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -13,7 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Ty import { AudioField } from "../../../fields/URLField"; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; -import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark, simulateMouseClick } from "../../../Utils"; +import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark, simulateMouseClick, returnEmptyString } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; @@ -802,6 +802,12 @@ export class DocumentViewInternal extends DocComponent StrListCast(this.props.Document._docFilters); + collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters); + @computed get showFilterIcon() { + return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" : + this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined + } rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; panelHeight = () => this.props.PanelHeight() - this.headerMargin; screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); @@ -832,7 +838,7 @@ export class DocumentViewInternal extends DocComponent; return
    this.props.PanelHeight() || 1; anchorStyleProvider = (doc: Opt, props: Opt, property: string): any => { switch (property) { + case StyleProp.ShowTitle: return ""; case StyleProp.PointerEvents: return "none"; case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox default: return this.props.styleProvider?.(doc, props, property); @@ -901,6 +908,8 @@ export class DocumentViewInternal extends DocComponent } + {this.showFilterIcon ? + { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })} + /> + : (null)}
    ; } } diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 55ea45bb8..b82d16677 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -2,10 +2,10 @@ import React = require("react"); import { observer } from "mobx-react"; import { documentSchema } from "../../../fields/documentSchemas"; import { makeInterface } from "../../../fields/Schema"; -import { returnFalse } from "../../../Utils"; -import { CollectionTreeView } from "../collections/CollectionTreeView"; +import { emptyFunction, returnFalse } from "../../../Utils"; import { ViewBoxBaseComponent } from "../DocComponent"; import { StyleProp } from "../StyleProvider"; +import { ComparisonBox } from "./ComparisonBox"; import { FieldView, FieldViewProps } from './FieldView'; import "./LinkBox.scss"; @@ -20,22 +20,15 @@ export class LinkBox extends ViewBoxBaseComponent( if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true); return
    - - + moveDocument={returnFalse} />
    ; } } \ No newline at end of file diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index b62a4dd56..a9d33f161 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -1,8 +1,9 @@ import React = require("react"); +import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import "./LinkDescriptionPopup.scss"; -import { observable, action } from "mobx"; +import { Doc } from "../../../fields/Doc"; import { LinkManager } from "../../util/LinkManager"; +import "./LinkDescriptionPopup.scss"; import { TaskCompletionBox } from "./TaskCompletedBox"; @@ -25,7 +26,7 @@ export class LinkDescriptionPopup extends React.Component<{}> { onDismiss = (add: boolean) => { LinkDescriptionPopup.descriptionPopup = false; if (add) { - LinkManager.currentLink && (LinkManager.currentLink.description = this.description); + LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).description = this.description); } } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index f0a502e31..30b4dc92a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -3,9 +3,9 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt, WidthSym, StrListCast } from "../../../fields/Doc"; import { documentSchema } from '../../../fields/documentSchemas'; -import { makeInterface } from "../../../fields/Schema"; +import { makeInterface, listSpec } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from '../../../fields/util'; @@ -25,6 +25,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @@ -249,9 +250,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - return 1; - } + contentScaling = () => 1; @computed get renderPdfView() { TraceMobx(); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 6a20ca14a..60a83d612 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -532,7 +532,8 @@ export class WebBox extends ViewBoxAnnotatableComponent {this.urlContent}
    ; @@ -575,10 +576,11 @@ export class WebBox extends ViewBoxAnnotatableComponent this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; - transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")]; + transparentFilter = () => [Utils.IsTransparentFilter(), ...this.basicFilter()]; + opaqueFilter = () => [Utils.IsOpaqueFilter(), ...this.basicFilter()]; render() { - const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined; + const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined; const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const scale = previewScale * (this.props.scaling?.() || 1); const renderAnnotations = (docFilters?: () => string[]) => @@ -593,7 +595,7 @@ export class WebBox extends ViewBoxAnnotatableComponent; return (
    + style={{ pointerEvents: this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none" }} >
    { let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1); - const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight); + const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight); if (scrollTo !== undefined) { focusSpeed = 500; @@ -502,8 +502,9 @@ export class PDFViewer extends React.Component { overlayTransform = () => this.scrollXf().scale(1 / this._zoomed); panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); - transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")]; + transparentFilter = () => [Utils.IsTransparentFilter(), ...this.basicFilter()]; + opaqueFilter = () => [Utils.IsOpaqueFilter(), ...this.basicFilter()]; @computed get overlayLayer() { const renderAnnotations = (docFilters?: () => string[]) => { select={emptyFunction} ContentScaling={this.contentZoom} bringToFront={emptyFunction} - docFilters={docFilters || this.props.docFilters} + docFilters={docFilters || this.basicFilter} dontRenderDocuments={docFilters ? false : true} CollectionView={undefined} ScreenToLocalTransform={this.overlayTransform} @@ -543,7 +544,7 @@ export class PDFViewer extends React.Component {
    ; } @computed get pdfViewerDiv() { - return
    ; + return
    ; } @computed get contentScaling() { return this.props.ContentScaling?.() || 1; } @computed get standinViews() { @@ -556,7 +557,7 @@ export class PDFViewer extends React.Component { render() { TraceMobx(); return
    -
    , key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists", toggle?: boolean, fieldSuffix?: string, append: boolean = true) { + export function setDocFilter(container: Opt, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) { if (!container) return; const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters"; const docFilters = Cast(container[filterField], listSpec("string"), []); -- cgit v1.2.3-70-g09d2 From 50886bf908a5a155849daa5920b42a86752f9d1b Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 15:09:55 -0400 Subject: fixed warnings --- src/client/DocServer.ts | 2 -- src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 3b376a0e7..e498a7cca 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -241,7 +241,6 @@ export namespace DocServer { // the field has been returned from the server const getSerializedField = Utils.EmitCallback(_socket, MessageStore.GetRefField, id); - console.log(id) // when the serialized RefField has been received, go head and begin deserializing it into an object. // Here, once deserialized, we also invoke .proto to 'load' the document's prototype, which ensures that all // future .proto calls on the Doc won't have to go farther than the cache to get their actual value. @@ -265,7 +264,6 @@ export namespace DocServer { } else { delete _cache[id]; } - console.log(id, field); return field; // either way, overwrite or delete any promises cached at this id (that we inserted as flags // to indicate that the field was in the process of being fetched). Now everything diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index ecff01e67..71642216c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -16,7 +16,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu { public showMarquee: () => void = unimplementedFunction; public hideMarquee: () => void = unimplementedFunction; public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; - public isShown = () => { return this._opacity > 0; } + public isShown = () => this._opacity > 0; constructor(props: Readonly<{}>) { super(props); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 35249c7f4..d2b4b0348 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -806,7 +806,7 @@ export class DocumentViewInternal extends DocComponent StrListCast(this.props.Document._docRangeFilters); @computed get showFilterIcon() { return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" : - this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined + this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined; } rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; panelHeight = () => this.props.PanelHeight() - this.headerMargin; -- cgit v1.2.3-70-g09d2 From b4a3dd6551a0ad6582c2a1a42168faae74ef1913 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 16:09:31 -0400 Subject: fixed link lines to text selections in PDF/Web. fixed pointer Events for anchor in PDF/Web to be active when doc is active. --- src/client/views/nodes/WebBox.tsx | 22 ++++++++++++++++------ src/client/views/pdf/Annotation.tsx | 5 ++++- src/client/views/pdf/PDFViewer.tsx | 23 +++++++++++++++++------ 3 files changed, 37 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 60a83d612..8e6586735 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -20,6 +20,7 @@ import { KeyCodes } from "../../util/KeyCodes"; import { Scripting } from "../../util/Scripting"; import { SnappingManager } from "../../util/SnappingManager"; import { undoBatch } from "../../util/UndoManager"; +import { MarqueeOptionsMenu } from "../collections/collectionFreeForm"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; @@ -31,11 +32,12 @@ import { MarqueeAnnotator } from "../MarqueeAnnotator"; import { AnchorMenu } from "../pdf/AnchorMenu"; import { Annotation } from "../pdf/Annotation"; import { SidebarAnnos } from "../SidebarAnnos"; +import { StyleProp } from "../StyleProvider"; +import { DocumentViewProps } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from "./LinkDocPreview"; import "./WebBox.scss"; import React = require("react"); -import { MarqueeOptionsMenu } from "../collections/collectionFreeForm"; const _global = (window /* browser */ || global /* node */) as any; const htmlToText = require("html-to-text"); @@ -541,9 +543,10 @@ export class WebBox extends ViewBoxAnnotatableComponent {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - )} + )}
    ; } @@ -577,8 +580,15 @@ export class WebBox extends ViewBoxAnnotatableComponent this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")]; - transparentFilter = () => [Utils.IsTransparentFilter(), ...this.basicFilter()]; - opaqueFilter = () => [Utils.IsOpaqueFilter(), ...this.basicFilter()]; + transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; + opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + childStyleProvider = (doc: (Doc | undefined), props: Opt, property: string): any => { + if (doc instanceof Doc && property === StyleProp.PointerEvents) { + if (doc.textInlineAnnotations) return "none"; + } + return this.props.styleProvider?.(doc, props, property); + } + pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none"; render() { const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined; const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; @@ -604,11 +614,11 @@ export class WebBox extends ViewBoxAnnotatableComponent; return (
    + style={{ pointerEvents: this.pointerEvents() }} >
    ) => void; + pointerEvents?: string; } @observer export class Annotation extends React.Component { render() { - return DocListCast(this.props.anno.textInlineAnnotations).map(a => ); + return DocListCast(this.props.anno.textInlineAnnotations).map(a => ); } } interface IRegionAnnotationProps extends IAnnotationProps { document: Doc; + pointerEvents?: string; } @observer class RegionAnnotation extends React.Component { @@ -96,6 +98,7 @@ class RegionAnnotation extends React.Component { width: NumCast(this.props.document._width), height: NumCast(this.props.document._height), opacity: this._brushed ? 0.5 : undefined, + pointerEvents: this.props.pointerEvents as any, backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor), }} >
    ); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 78adedf5b..3f7f38bdf 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -16,10 +16,13 @@ import { CompiledScript, CompileScript } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; import { SharingManager } from "../../util/SharingManager"; import { SnappingManager } from "../../util/SnappingManager"; +import { MarqueeOptionsMenu } from "../collections/collectionFreeForm"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { MarqueeAnnotator } from "../MarqueeAnnotator"; +import { DocumentViewProps } from "../nodes/DocumentView"; import { FieldViewProps } from "../nodes/FieldView"; import { LinkDocPreview } from "../nodes/LinkDocPreview"; +import { StyleProp } from "../StyleProvider"; import { AnchorMenu } from "./AnchorMenu"; import { Annotation } from "./Annotation"; import "./PDFViewer.scss"; @@ -481,11 +484,12 @@ export class PDFViewer extends React.Component { } } + pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none"; @computed get annotationLayer() { + const pe = this.pointerEvents(); return
    {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - ) - } + )}
    ; } @@ -503,8 +507,15 @@ export class PDFViewer extends React.Component { panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")]; - transparentFilter = () => [Utils.IsTransparentFilter(), ...this.basicFilter()]; - opaqueFilter = () => [Utils.IsOpaqueFilter(), ...this.basicFilter()]; + transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; + opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + childStyleProvider = (doc: (Doc | undefined), props: Opt, property: string): any => { + if (doc instanceof Doc && property === StyleProp.PointerEvents) { + if (doc.textInlineAnnotations) return "none"; + return "all"; + } + return this.props.styleProvider?.(doc, props, property); + } @computed get overlayLayer() { const renderAnnotations = (docFilters?: () => string[]) => { ContentScaling={this.contentZoom} bringToFront={emptyFunction} docFilters={docFilters || this.basicFilter} + styleProvider={this.childStyleProvider} dontRenderDocuments={docFilters ? false : true} CollectionView={undefined} ScreenToLocalTransform={this.overlayTransform} - renderDepth={this.props.renderDepth + 1} - childPointerEvents={true} />; + renderDepth={this.props.renderDepth + 1} />; return
    Date: Fri, 17 Sep 2021 17:51:46 -0400 Subject: fixed following links to documents in sidebars that haven't been opened to still open the sidebar and show the document. required a change to how ProxyFields set field promises and use field caches. --- src/client/views/SidebarAnnos.tsx | 2 +- src/fields/Proxy.ts | 66 ++++++--------------------------------- 2 files changed, 11 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 659eb86d1..6a5b0a3ae 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -68,8 +68,8 @@ export class SidebarAnnos extends React.Component { if (DocListCast(this.props.rootDoc[this.sidebarKey]).includes(doc)) { if (this.props.layoutDoc[this.filtersKey]) { this.props.layoutDoc[this.filtersKey] = new List(); - return true; } + return true; } return false; } diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts index 62734d3d2..07553f17c 100644 --- a/src/fields/Proxy.ts +++ b/src/fields/Proxy.ts @@ -9,72 +9,33 @@ import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; import { Plugins } from "./util"; -function deserializeProxy(field: any) { - if (!field.cache) { - field.cache = DocServer.GetCachedRefField(field.fieldId) as any; - } -} -@Deserializable("proxy", deserializeProxy) +@Deserializable("proxy") export class ProxyField extends ObjectField { constructor(); constructor(value: T); constructor(fieldId: string); constructor(value?: T | string) { super(); - if (typeof value === "string") { - this.cache = DocServer.GetCachedRefField(value) as any; - this.fieldId = value; - } else if (value) { - this.cache = value; - this.fieldId = value[Id]; - } - } - - [Copy]() { - if (this.cache) return new ProxyField(this.cache); - return new ProxyField(this.fieldId); + this.fieldId = typeof value === "string" ? value : (value?.[Id] ?? ""); } - [ToScriptString]() { - return "invalid"; - } - [ToString]() { - return "ProxyField"; - } + [Copy]() { return new ProxyField((this.cache ?? this.fieldId) as T); } + [ToScriptString]() { return "invalid"; } + [ToString]() { return "ProxyField"; } @serializable(primitive()) readonly fieldId: string = ""; - - // This getter/setter and nested object thing is - // because mobx doesn't play well with observable proxies - @observable.ref - private _cache: { readonly field: T | undefined } = { field: undefined }; - private get cache(): T | undefined { - return this._cache.field; - } - private set cache(field: T | undefined) { - this._cache = { field }; - } - private failed = false; private promise?: Promise; + private get cache(): T | undefined { return DocServer.GetCachedRefField(this.fieldId) as T } + value(): T | undefined | FieldWaiting { - if (this.cache) { - return this.cache; - } - if (this.failed) { - return undefined; - } + if (this.cache) return this.cache; + if (this.failed) return undefined; if (!this.promise) { - const cached = DocServer.GetCachedRefField(this.fieldId); - if (cached !== undefined) { - runInAction(() => this.cache = cached as any); - return cached as any; - } this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => { this.promise = undefined; - this.cache = field; if (field === undefined) this.failed = true; return field; })); @@ -83,14 +44,7 @@ export class ProxyField extends ObjectField { } promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; } setPromise(promise: any) { - this.promise = promise; - } - @action - setValue(field: any) { - this.promise = undefined; - this.cache = field; - if (field === undefined) this.failed = true; - return field; + if (this.cache === undefined) this.promise = promise; } } -- cgit v1.2.3-70-g09d2 From 7e3bb99b81dd9494c927abacc2ebf920282f3ccb Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 19:13:23 -0400 Subject: reverting last change. --- src/fields/Proxy.ts | 66 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts index 07553f17c..62734d3d2 100644 --- a/src/fields/Proxy.ts +++ b/src/fields/Proxy.ts @@ -9,33 +9,72 @@ import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; import { Plugins } from "./util"; -@Deserializable("proxy") +function deserializeProxy(field: any) { + if (!field.cache) { + field.cache = DocServer.GetCachedRefField(field.fieldId) as any; + } +} +@Deserializable("proxy", deserializeProxy) export class ProxyField extends ObjectField { constructor(); constructor(value: T); constructor(fieldId: string); constructor(value?: T | string) { super(); - this.fieldId = typeof value === "string" ? value : (value?.[Id] ?? ""); + if (typeof value === "string") { + this.cache = DocServer.GetCachedRefField(value) as any; + this.fieldId = value; + } else if (value) { + this.cache = value; + this.fieldId = value[Id]; + } + } + + [Copy]() { + if (this.cache) return new ProxyField(this.cache); + return new ProxyField(this.fieldId); } - [Copy]() { return new ProxyField((this.cache ?? this.fieldId) as T); } - [ToScriptString]() { return "invalid"; } - [ToString]() { return "ProxyField"; } + [ToScriptString]() { + return "invalid"; + } + [ToString]() { + return "ProxyField"; + } @serializable(primitive()) readonly fieldId: string = ""; + + // This getter/setter and nested object thing is + // because mobx doesn't play well with observable proxies + @observable.ref + private _cache: { readonly field: T | undefined } = { field: undefined }; + private get cache(): T | undefined { + return this._cache.field; + } + private set cache(field: T | undefined) { + this._cache = { field }; + } + private failed = false; private promise?: Promise; - private get cache(): T | undefined { return DocServer.GetCachedRefField(this.fieldId) as T } - value(): T | undefined | FieldWaiting { - if (this.cache) return this.cache; - if (this.failed) return undefined; + if (this.cache) { + return this.cache; + } + if (this.failed) { + return undefined; + } if (!this.promise) { + const cached = DocServer.GetCachedRefField(this.fieldId); + if (cached !== undefined) { + runInAction(() => this.cache = cached as any); + return cached as any; + } this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => { this.promise = undefined; + this.cache = field; if (field === undefined) this.failed = true; return field; })); @@ -44,7 +83,14 @@ export class ProxyField extends ObjectField { } promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; } setPromise(promise: any) { - if (this.cache === undefined) this.promise = promise; + this.promise = promise; + } + @action + setValue(field: any) { + this.promise = undefined; + this.cache = field; + if (field === undefined) this.failed = true; + return field; } } -- cgit v1.2.3-70-g09d2 From 75c47275853bfe422d6ae8a53be8ffe1996e1e76 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 19:46:42 -0400 Subject: more conservative fix for previous. --- src/fields/Proxy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts index 62734d3d2..f01b502c9 100644 --- a/src/fields/Proxy.ts +++ b/src/fields/Proxy.ts @@ -79,7 +79,7 @@ export class ProxyField extends ObjectField { return field; })); } - return this.promise as any; + return DocServer.GetCachedRefField(this.fieldId) ?? (this.promise as any); } promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; } setPromise(promise: any) { -- cgit v1.2.3-70-g09d2 From fcab3f96513c759ca67281e871b970d5691348af Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 20:12:00 -0400 Subject: fixed making link to selected region in video --- src/client/views/collections/CollectionStackedTimeline.tsx | 5 +++-- src/client/views/nodes/VideoBox.tsx | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 65022fdfd..89da6692a 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -372,10 +372,11 @@ export class CollectionStackedTimeline extends CollectionSubView< startTag: string, endTag: string, anchorStartTime?: number, - anchorEndTime?: number + anchorEndTime?: number, + docAnchor?: Doc ) { if (anchorStartTime === undefined) return rootDoc; - const anchor = Docs.Create.LabelDocument({ + const anchor = docAnchor ?? Docs.Create.LabelDocument({ title: ComputedField.MakeFunction( `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])` ) as any, diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 90de3227f..e3ba638b3 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -30,6 +30,7 @@ import { DragManager } from "../../util/DragManager"; import { DocumentManager } from "../../util/DocumentManager"; import { DocumentType } from "../../documents/DocumentTypes"; import { Tooltip } from "@material-ui/core"; +import { AnchorMenu } from "../pdf/AnchorMenu"; const path = require('path'); type VideoDocument = makeInterface<[typeof documentSchema]>; @@ -74,7 +75,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const timecode = Cast(this.layoutDoc._currentTimecode, "number", null); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined) || this.rootDoc; + const marquee = AnchorMenu.Instance.GetAnchor?.(); + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined, undefined, marquee) || this.rootDoc; } videoLoad = () => { -- cgit v1.2.3-70-g09d2 From dfe45d9162857b35574f8f809bca132c0467189a Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 20:37:52 -0400 Subject: fixed setting time of audio doc when clicking on audiotag in RTF. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 3c2ff2df5..3e5c4456c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1187,9 +1187,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if ((e.target as any).tagName === "AUDIOTAG") { e.preventDefault(); e.stopPropagation(); + const timecode = Number((e.target as any)?.dataset?.timecode); DocServer.GetRefField((e.target as any)?.dataset?.audioid || 0).then(anchor => { if (anchor instanceof Doc) { - const timecode = NumCast(anchor.timecodeToShow, 0); + // const timecode = NumCast(anchor.timecodeToShow, 0); const audiodoc = anchor.annotationOn as Doc; const func = () => { const docView = DocumentManager.Instance.getDocumentView(audiodoc); -- cgit v1.2.3-70-g09d2 From ace42c6705d9f523f6f7af8cc22d0e6a3b5eeace Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Sep 2021 21:33:24 -0400 Subject: stop propagation of keybaord events in EquationBox to allow cursor controls w/o moving the document --- src/client/views/nodes/EquationBox.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index dacafcdf4..11ef6562f 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -88,6 +88,7 @@ export class EquationBox extends ViewBoxBaseComponent e.stopPropagation()} > Date: Mon, 20 Sep 2021 10:45:28 -0400 Subject: changed link previews to show document, not context, unless document is a marker. de select documentViews when unmounted. --- src/client/util/DocumentManager.ts | 2 ++ src/client/views/nodes/LinkDocPreview.tsx | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index dfec9823b..b66befb08 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -10,6 +10,7 @@ import { LightboxView } from '../views/LightboxView'; import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { Scripting } from './Scripting'; +import { SelectionManager } from './SelectionManager'; export class DocumentManager { @@ -62,6 +63,7 @@ export class DocumentManager { const index = this.DocumentViews.indexOf(view); index !== -1 && this.DocumentViews.splice(index, 1); } + SelectionManager.DeselectView(view); }); //gets all views diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 126a37eb8..424083dac 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -4,7 +4,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from "mobx-react"; import wiki from "wikijs"; import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from "../../../fields/Doc"; -import { NumCast, StrCast } from "../../../fields/Types"; +import { NumCast, StrCast, Cast } from "../../../fields/Types"; import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils"; import { DocServer } from '../../DocServer'; import { Docs, DocUtils } from "../../documents/Documents"; @@ -14,6 +14,7 @@ import { undoBatch } from '../../util/UndoManager'; import { DocumentView, DocumentViewSharedProps } from "./DocumentView"; import './LinkDocPreview.scss'; import React = require("react"); +import { DocumentType } from '../../documents/DocumentTypes'; interface LinkDocPreviewProps { linkDoc?: Doc; @@ -85,7 +86,7 @@ export class LinkDocPreview extends React.Component { this._linkDoc = DocListCast(anchor.links)[0]; this._linkSrc = anchor; const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); - this._targetDoc = linkTarget?.annotationOn as Doc ?? linkTarget; + this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; this._toolTipText = ""; } })); -- cgit v1.2.3-70-g09d2 From 240f2c9bda4a9a32d84d1de93820f6fbc8eef458 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Sep 2021 12:24:34 -0400 Subject: fixed equations/ink to work properly when not fitwidth in lightbox. don't show titles for ink or equations using fields now. --- src/client/documents/Documents.ts | 8 +++++--- src/client/views/DocumentDecorations.tsx | 20 ++++++++++-------- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/EquationBox.scss | 3 +++ src/client/views/nodes/EquationBox.tsx | 7 ++++++- .../views/nodes/formattedText/FormattedTextBox.tsx | 24 ++++++++++------------ 6 files changed, 37 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e8185400e..fafbc4a7d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -430,7 +430,7 @@ export namespace Docs { }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: ComputedField.MakeFunction("links(self)") as any, hideResizeHandles: true, hideDecorationTitle: true } }], [DocumentType.FUNCPLOT, { layout: { view: FunctionPlotBox, dataField: defaultDataKey }, @@ -463,9 +463,9 @@ export namespace Docs { layout: { view: CollectionView, dataField: defaultDataKey }, options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true } }], - [DocumentType.INK, { + [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method layout: { view: InkingStroke, dataField: defaultDataKey }, - options: { _fontFamily: "cursive", backgroundColor: "transparent", links: ComputedField.MakeFunction("links(self)") as any } + options: { links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.SCREENSHOT, { layout: { view: ScreenshotBox, dataField: defaultDataKey }, @@ -716,6 +716,8 @@ export namespace Docs { I.type = DocumentType.INK; I.layout = InkingStroke.LayoutString("data"); I.color = color; + I.hideDecorationTitle = true; // don't show title when selected + I.hideOpenButton = true; // don't show open full screen button when selected I.fillColor = fillColor; I.strokeWidth = strokeWidth; I.strokeBezier = strokeBezier; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3d6f157b6..d785d5419 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -388,10 +388,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] })) .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => { Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth) - ({ - X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width, - Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height - }))); + ({ + X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width, + Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height + }))); Doc.SetNativeWidth(doc, undefined); Doc.SetNativeHeight(doc, undefined); }); @@ -421,7 +421,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } - const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup); + const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles; + const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle; + const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton); const canDelete = SelectionManager.Views().some(docView => { const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) && @@ -473,12 +475,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> {!canDelete ?
    : topBtn("close", "times", undefined, this.onCloseClick, "Close")} - {seldoc.props.hideDecorationTitle || seldoc.props.Document.type === DocumentType.EQUATION ? (null) : titleArea} - {seldoc.props.hideResizeHandles || seldoc.props.Document.type === DocumentType.EQUATION ? (null) : + {hideTitle ? (null) : titleArea}{!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} + + {hideResizers ? (null) : <> - {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) : + {SelectionManager.Views().length !== 1 || hideTitle ? (null) : topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)} - {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
    e.preventDefault()} />
    e.preventDefault()} />
    e.preventDefault()} /> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d2b4b0348..60ceac007 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1168,7 +1168,7 @@ export class DocumentView extends React.Component { } const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse(); const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)]; - if (this.docView.props.LayoutTemplateString?.includes("LinkAnchorBox")) { + if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); if (docuBox.length) return docuBox[0].getBoundingClientRect(); } diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss index e69de29bb..6c9d53d10 100644 --- a/src/client/views/nodes/EquationBox.scss +++ b/src/client/views/nodes/EquationBox.scss @@ -0,0 +1,3 @@ +.equationBox-cont { + transform-origin: top left; +} \ No newline at end of file diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 11ef6562f..f1f802c13 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -84,8 +84,13 @@ export class EquationBox extends ViewBoxBaseComponent !e.ctrlKey && e.stopPropagation()} + const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + return (
    !e.ctrlKey && e.stopPropagation()} style={{ + transform: `scale(${scale})`, + width: `${100 / scale}%`, + height: `${100 / scale}%`, pointerEvents: !this.props.isSelected() ? "none" : undefined, }} onKeyDown={e => e.stopPropagation()} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 3e5c4456c..ebd509669 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1469,19 +1469,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } tryUpdateScrollHeight = () => { - if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { - const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); - const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; - if (children) { - const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins); - const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); - if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; - if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { - setScrollHeight(); - } else { - setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... - } + const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); + const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; + if (children) { + const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins); + const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); + if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; + if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { + setScrollHeight(); + } else { + setTimeout(setScrollHeight, 10); // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... } } } -- cgit v1.2.3-70-g09d2 From e295f6694bed9a3a35a0858c8f17745ef1506f51 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Sep 2021 12:52:38 -0400 Subject: replaced convert to ink in marquee menu with create Group --- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 38 ++++++++++++++-------- 1 file changed, 25 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 71642216c..1f59f9732 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -3,13 +3,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { observer } from "mobx-react"; import { unimplementedFunction } from "../../../../Utils"; +import { DocumentType } from "../../../documents/DocumentTypes"; +import { SelectionManager } from "../../../util/SelectionManager"; import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; @observer export class MarqueeOptionsMenu extends AntimodeMenu { static Instance: MarqueeOptionsMenu; - public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; + public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction; public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; @@ -26,42 +28,52 @@ export class MarqueeOptionsMenu extends AntimodeMenu { render() { const presPinWithViewIcon = ; const buttons = [ -
    Create a Collection
    } placement="bottom"> + Create a Collection
    } placement="bottom"> , -
    Summarize Documents
    } placement="bottom"> + Create a Grouping
    } placement="bottom"> , -
    Delete Documents
    } placement="bottom"> + Summarize Documents
    } placement="bottom"> , -
    Change to Text
    } placement="bottom"> + Delete Documents
    } placement="bottom"> , -
    Pin with selected region
    } placement="bottom"> + Pin with selected region
    } placement="bottom"> , ]; + if (false && !SelectionManager.Views().some(v => v.props.Document.type !== DocumentType.INK)) { + buttons.push( + Change to Text
    } placement="bottom"> + + ); + } return this.getElement(buttons); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 6cdfb4f5cfa4233b0879ad638524dba777471895 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Sep 2021 23:08:14 -0400 Subject: fixed retrieving fillColor for color button menu option. --- src/client/views/nodes/button/FontIconBox.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 9d1ef937f..adcac0aef 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -579,7 +579,6 @@ Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: bo return "#FFFFFF"; } } - if (selected?.type === DocumentType.INK) selected.fillColor = color; if (selected) selected._backgroundColor = color; Doc.UserDoc()._fontColor = color; }); @@ -823,7 +822,7 @@ Scripting.addGlobal(function setFillColor(color?: string, checkResult?: boolean) const selected = SelectionManager.Docs().lastElement(); if (checkResult) { if (selected?.type === DocumentType.INK) { - return StrCast(selected._backgroundColor); + return StrCast(selected.fillColor); } return ActiveFillColor(); } -- cgit v1.2.3-70-g09d2 From b65157915070c520c3d6a3e67052b4a2b1b7b127 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 21 Sep 2021 10:27:31 -0400 Subject: tried to make captions more readable when zoomed out by playing with scaling and overflow. removed sidebar button from captions. --- src/client/views/nodes/DocumentView.scss | 1 + src/client/views/nodes/DocumentView.tsx | 11 +++++++++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 7f164ca48..1ec7bf72a 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -175,6 +175,7 @@ position: absolute; bottom: 0; width: 100%; + overflow-y: scroll; transform-origin: bottom left; opacity: 0.1; transition: opacity 0.5s; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 60ceac007..3e15ed661 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -972,19 +972,26 @@ export class DocumentViewInternal extends DocComponent, props: Opt, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption"); @computed get innards() { TraceMobx(); + const ffscale = (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1); const showTitle = this.ShowTitle?.split(":")[0]; const showTitleHover = this.ShowTitle?.includes(":hover"); const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined; const captionView = !showCaption ? (null) :
    + style={{ + pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined, + minWidth: 50 * ffscale, + maxHeight: `max(100%, ${20 * ffscale}px)` + }}> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ebd509669..617fa0bee 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -833,7 +833,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { - autoHeight && this.props.setHeight(marginsHeight + Math.max(sidebarHeight, textHeight)); + autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight)); }, { fireImmediately: true }); this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { -- cgit v1.2.3-70-g09d2 From eb50e46332c2f0b48c7cb165d55245aab317ef08 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 21 Sep 2021 19:43:32 -0400 Subject: fixed up some darkScheme css, enabled comic mode for developers, enabled opening up fields on dashboards in myDocuments --- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 30 ++++++++++------ src/client/util/SelectionManager.ts | 14 ++------ src/client/util/SettingsManager.tsx | 19 +++++----- src/client/views/ContextMenu.scss | 1 - src/client/views/DocumentDecorations.scss | 28 +++++++++++---- src/client/views/DocumentDecorations.tsx | 42 +++++++++++++--------- src/client/views/MainView.scss | 12 +++---- src/client/views/MainView.tsx | 14 ++++---- src/client/views/StyleProvider.tsx | 5 +-- .../views/collections/CollectionSchemaView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 9 +++-- .../CollectionFreeFormLayoutEngines.tsx | 3 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 ++- .../collectionSchema/CollectionSchemaView.tsx | 7 ++-- src/client/views/nodes/DocumentView.tsx | 6 ++-- src/client/views/nodes/button/FontIconBox.tsx | 2 -- .../views/nodes/formattedText/DashDocView.tsx | 3 +- src/mobile/MobileInterface.tsx | 4 +-- 19 files changed, 118 insertions(+), 89 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fafbc4a7d..206f65bfd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -862,7 +862,7 @@ export namespace Docs { export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true }); const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true }); - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } export function DirectoryImportDocument(options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d5dc9e2be..568a9ddbd 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -20,6 +20,7 @@ import { Networking } from "../Network"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; +import { TreeView } from "../views/collections/TreeView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; @@ -38,7 +39,6 @@ import { ColorScheme } from "./SettingsManager"; import { SharingManager } from "./SharingManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; -import { TreeView } from "../views/collections/TreeView"; interface Button { title?: string; @@ -816,16 +816,19 @@ export class CurrentUserUtils { _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true, explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." })); - // const toggleTheme = ScriptField.MakeScript(`Doc.UserDoc().darkScheme = !Doc.UserDoc().darkScheme`); - // const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); - // const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`); + const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`); + const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); + const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`); const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`); const removeDashboard = ScriptField.MakeScript('removeDashboard(self)'); - (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, shareDashboard!, removeDashboard!]); - (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]); - (doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "user-friends", "times"]); - // (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, toggleTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]); - // (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]); + const developerFilter = ScriptField.MakeFunction('!IsNoviceMode()'); + // (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, shareDashboard!, removeDashboard!]); + // (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]); + // (doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "user-friends", "times"]); + (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, toggleDarkTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]); + (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]); + (doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "chalkboard", "tv", "camera", "users", "times"]); + (doc.myDashboards as any as Doc).childContextMenuFilters = new List([undefined as any, developerFilter, developerFilter, developerFilter, undefined as any, undefined as any]); } return doc.myDashboards as any as Doc; } @@ -1302,7 +1305,6 @@ export class CurrentUserUtils { }, { fireImmediately: true }); // Document properties on load doc.system = true; - doc.darkScheme = ColorScheme.Dark; doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; doc._raiseWhenDragged = true; @@ -1321,7 +1323,6 @@ export class CurrentUserUtils { doc.fontColor = StrCast(doc.fontColor, "black"); doc.fontHighlight = StrCast(doc.fontHighlight, ""); doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false); - doc.activeCollectionBackground = StrCast(doc.activeCollectionBackground, "white"); doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); doc.noviceMode = BoolCast(doc.noviceMode, true); doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // @@ -1358,6 +1359,10 @@ export class CurrentUserUtils { // }); setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); + if (doc.activeDashboard instanceof Doc) { + // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) + doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; + } return doc; } @@ -1637,4 +1642,7 @@ Scripting.addGlobal(function makeTopLevelFolder() { const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined }; return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder); +}); +Scripting.addGlobal(function toggleComicMode() { + Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }); \ No newline at end of file diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index bac13373c..e507ec3bf 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -2,7 +2,6 @@ import { action, observable, ObservableMap } from "mobx"; import { computedFn } from "mobx-utils"; import { Doc, Opt } from "../../fields/Doc"; import { DocumentType } from "../documents/DocumentTypes"; -import { CollectionSchemaView } from "../views/collections/collectionSchema/CollectionSchemaView"; import { CollectionViewType } from "../views/collections/CollectionView"; import { DocumentView } from "../views/nodes/DocumentView"; @@ -13,12 +12,10 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; SelectedViews: ObservableMap = new ObservableMap(); @observable SelectedSchemaDocument: Doc | undefined; - @observable SelectedSchemaCollection: CollectionSchemaView | undefined; @action - SelectSchemaView(collectionView: Opt, doc: Opt) { + SelectSchemaViewDoc(doc: Opt) { manager.SelectedSchemaDocument = doc; - manager.SelectedSchemaCollection = collectionView; } @action SelectView(docView: DocumentView, ctrlPressed: boolean): void { @@ -33,7 +30,6 @@ export namespace SelectionManager { } else if (!ctrlPressed && Array.from(manager.SelectedViews.entries()).length > 1) { Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false)); manager.SelectedSchemaDocument = undefined; - manager.SelectedSchemaCollection = undefined; manager.SelectedViews.clear(); manager.SelectedViews.set(docView, docView.rootDoc); } @@ -47,7 +43,6 @@ export namespace SelectionManager { } @action DeselectAll(): void { - manager.SelectedSchemaCollection = undefined; manager.SelectedSchemaDocument = undefined; Array.from(manager.SelectedViews.keys()).map(dv => dv.props.whenChildContentsActiveChanged(false)); manager.SelectedViews.clear(); @@ -62,8 +57,8 @@ export namespace SelectionManager { export function SelectView(docView: DocumentView, ctrlPressed: boolean): void { manager.SelectView(docView, ctrlPressed); } - export function SelectSchemaView(colSchema: Opt, document: Opt): void { - manager.SelectSchemaView(colSchema, document); + export function SelectSchemaViewDoc(document: Opt): void { + manager.SelectSchemaViewDoc(document); } const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed @@ -96,9 +91,6 @@ export namespace SelectionManager { export function SelectedSchemaDoc(): Doc | undefined { return manager.SelectedSchemaDocument; } - export function SelectedSchemaCollection(): CollectionSchemaView | undefined { - return manager.SelectedSchemaCollection; - } export function Docs(): Doc[] { return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking); } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index bd91db779..6a26dfdc7 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -19,9 +19,9 @@ export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; export enum ColorScheme { - Dark = "Dark", - Light = "Light", - System = "Match System" + Dark = "-Dark", + Light = "-Light", + System = "-MatchSystem" } @observer @@ -38,7 +38,7 @@ export class SettingsManager extends React.Component<{}> { @observable activeTab = "Accounts"; @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; } - @computed get colorScheme() { return Doc.UserDoc().colorScheme; } + @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard.colorScheme; } constructor(props: {}) { super(props); @@ -81,16 +81,16 @@ export class SettingsManager extends React.Component<{}> { const scheme: ColorScheme = (e.currentTarget as any).value; switch (scheme) { case ColorScheme.Light: - Doc.UserDoc().colorScheme = ColorScheme.Light; + CurrentUserUtils.ActiveDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" }); break; case ColorScheme.Dark: - Doc.UserDoc().colorScheme = ColorScheme.Dark; + CurrentUserUtils.ActiveDashboard.colorScheme = ColorScheme.Dark; addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" }); break; case ColorScheme.System: default: window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { - Doc.UserDoc().colorScheme = e.matches ? ColorScheme.Dark : ColorScheme.Light; + CurrentUserUtils.ActiveDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) }); break; } @@ -119,6 +119,7 @@ export class SettingsManager extends React.Component<{}> {
    ; const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System]; + const schemeMap = ["Light", "Dark", "Match system"]; return
    @@ -132,8 +133,8 @@ export class SettingsManager extends React.Component<{}> {
    Color Scheme
    - + {colorSchemes.map((scheme, i) => )}
    diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index 47ae0424b..ea24dbf6d 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -8,7 +8,6 @@ flex-direction: column; background: whitesmoke; border-radius: 3px; - border: solid $light-gray 1px; } // .contextMenu-item:first-child { diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index a9f50f81b..d8ad47ecb 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -2,10 +2,14 @@ $linkGap: 3px; +.documentDecorations-Dark, .documentDecorations { position: absolute; z-index: 2000; } +.documentDecorations-Dark { + background: dimgray; +} .documentDecorations-container { z-index: $docDecorations-zindex; position: absolute; @@ -50,12 +54,17 @@ $linkGap: 3px; pointer-events: auto; background: $medium-gray; opacity: 0.1; - &:hover { opacity: 1; } } + .documentDecorations-resizer-Dark + { + background: $light-gray; + opacity: 0.2; + } + .documentDecorations-topLeftResizer, .documentDecorations-leftResizer, .documentDecorations-bottomLeftResizer { @@ -221,6 +230,7 @@ $linkGap: 3px; cursor: ns-resize; } + .documentDecorations-title-Dark, .documentDecorations-title { opacity: 1; grid-column-start: 2; @@ -233,14 +243,22 @@ $linkGap: 3px; height: 22px; position: absolute; - .documentDecorations-titleSpan { + .documentDecorations-titleSpan, + .documentDecorations-titleSpan-Dark { width: 100%; border-radius: 8px; - background: #ffffffcf; + background: #ffffffa0; position: absolute; display: inline-block; cursor: move; } + .documentDecorations-titleSpan-Dark { + background: hsla(0, 0%, 0%, 0.412); + } + } + .documentDecorations-title-Dark { + color: white; + background: black; } .documentDecorations-contextMenu { @@ -439,10 +457,6 @@ $linkGap: 3px; } } -.documentDecorations-darkScheme { - background: dimgray; -} - #template-list { position: absolute; top: 25px; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d785d5419..e9a54d6a5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -8,7 +8,7 @@ import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSy import { Document } from '../../fields/documentSchemas'; import { InkField } from "../../fields/InkField"; import { ScriptField } from '../../fields/ScriptField'; -import { Cast, NumCast } from "../../fields/Types"; +import { Cast, NumCast, StrCast } from "../../fields/Types"; import { GetEffectiveAcl } from '../../fields/util'; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; import { Docs } from "../documents/Documents"; @@ -27,6 +27,8 @@ import { LightboxView } from './LightboxView'; import { DocumentView } from "./nodes/DocumentView"; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import React = require("react"); +import { dark } from '@material-ui/core/styles/createPalette'; +import { color } from 'd3-color'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> { @@ -241,7 +243,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P InkStrokeProperties.Instance?._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK) .forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc)); - if (fixedAspect && (this._resizeHdlId === "documentDecorations-bottomRightResizer" || this._resizeHdlId === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles + const resizeHdl = this._resizeHdlId.split(" ")[0]; + if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles const project = (p: number[], a: number[], b: number[]) => { const atob = [b[0] - a[0], b[1] - a[1]]; const atop = [p[0] - a[0], p[1] - a[1]]; @@ -264,7 +267,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P this._snapY = thisPt.y; let dragBottom = false, dragRight = false, dragBotRight = false; let dX = 0, dY = 0, dW = 0, dH = 0; - switch (this._resizeHdlId) { + switch (this._resizeHdlId.split(" ")[0]) { case "": break; case "documentDecorations-topLeftResizer": dX = -1; @@ -437,11 +440,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
    ); + const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); const titleArea = this._edtingTitle ? - this.titleBlur()} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> : + this.titleBlur()} + onChange={action(e => this._accumulatedTitle = e.target.value)} + onKeyPress={this.titleEntered} /> :
    - {`${this.selectionTitle}`} + {`${this.selectionTitle}`}
    ; let inMainMenuPanel = false; @@ -457,8 +466,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); const useRotation = seldoc.rootDoc.type === DocumentType.INK; + const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : ""; - return (
    + return (
    {SelectionManager.Views().length !== 1 || hideTitle ? (null) : topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)} -
    e.preventDefault()} /> -
    e.preventDefault()} /> -
    e.preventDefault()} /> -
    e.preventDefault()} /> -
    -
    e.preventDefault()} /> -
    e.preventDefault()} /> -
    e.preventDefault()} /> -
    e.preventDefault()} /> +
    e.preventDefault()} /> +
    e.preventDefault()} /> +
    e.preventDefault()} /> +
    e.preventDefault()} /> +
    +
    e.preventDefault()} /> +
    e.preventDefault()} /> +
    e.preventDefault()} /> +
    e.preventDefault()} /> {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) : topBtn("selector", "arrow-alt-circle-up", undefined, this.onSelectorClick, "tap to select containing document")} diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 4f871f5ec..7fa841002 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -41,7 +41,7 @@ } .mainView-container, -.mainView-container-dark { +.mainView-container-Dark { width: 100%; height: 100%; position: absolute; @@ -65,7 +65,7 @@ } } -.mainView-container-dark { +.mainView-container-Dark { color: $light-gray; .lm_goldenlayout { @@ -91,7 +91,7 @@ .contextMenu-cont, .contextMenu-item { - background: $medium-gray; + background: $dark-gray; } .contextMenu-item:hover { @@ -144,7 +144,7 @@ } } -.mainView-innerContent, .mainView-innerContent-dark { +.mainView-innerContent, .mainView-innerContent-Dark { display: contents; flex-direction: row; position: relative; @@ -175,7 +175,7 @@ .mainView-libraryHandle { background-color: $light-gray; } -.mainView-innerContent-dark +.mainView-innerContent-Dark { .propertiesView { background-color: #252525; @@ -198,7 +198,7 @@ background: #353535; } } -.mainView-container-dark { +.mainView-container-Dark { .contextMenu-cont { background: $medium-gray; color: $white; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d854f118f..c99ba447c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -26,7 +26,7 @@ import { HistoryUtil } from '../util/History'; import { Hypothesis } from '../util/HypothesisUtils'; import { Scripting } from '../util/Scripting'; import { SelectionManager } from '../util/SelectionManager'; -import { SettingsManager } from '../util/SettingsManager'; +import { SettingsManager, ColorScheme } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; @@ -88,7 +88,7 @@ export class MainView extends React.Component { @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; } @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; } @computed private get userDoc() { return Doc.UserDoc(); } - @computed private get darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); } + @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); } @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; } @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } @@ -430,10 +430,10 @@ export class MainView extends React.Component { const transform = this._leftMenuFlyoutWidth ? 'translate(-28px, 0px)' : undefined; return <> {this.leftMenuPanel} -
    +
    {this.flyout}
    - +
    @@ -441,7 +441,7 @@ export class MainView extends React.Component { {this.dockingContent}
    - +
    {this.propertiesWidth() < 10 ? (null) : } @@ -459,7 +459,7 @@ export class MainView extends React.Component { this._dashUIHeight = r.getBoundingClientRect().height; })).observe(r); }} style={{ - color: this.darkScheme ? "rgb(205,205,205)" : "black", + color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black", height: `calc(100% - ${this.topOfDashUI}px)`, width: "100%", }} > @@ -601,7 +601,7 @@ export class MainView extends React.Component { } render() { - return (
    ((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)} ref={r => { r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3c88a4830..1eb7a222e 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -22,6 +22,7 @@ import "./StyleProvider.scss"; import React = require("react"); import Color = require('color'); import { lightOrDark } from '../../Utils'; +import { ColorScheme } from '../util/SettingsManager'; export enum StyleLayers { Background = "background" @@ -49,7 +50,7 @@ export enum StyleProp { FontSize = "fontSize", // size of text font } -function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); } +function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; } function toggleBackground(doc: Doc) { UndoManager.RunInBatch(() => runInAction(() => { @@ -143,7 +144,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? Doc.UserDoc().activeCollectionNestedBackground : - Doc.UserDoc().activeCollectionBackground))); + Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE)))); break; //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 6a22acae8..3ea190a98 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -353,7 +353,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action setFocused = (doc: Doc) => this._focusedTable = doc; @action setPreviewDoc = (doc: Opt) => { - SelectionManager.SelectSchemaView(this, doc); + SelectionManager.SelectSchemaViewDoc(doc); this._previewDoc = doc; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 97de097e0..a3da0e0e4 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -481,13 +481,16 @@ export class TreeView extends React.Component { } @computed get validExpandViewTypes() { - if (this.doc.viewType === CollectionViewType.Docking) return [this.fieldKey]; + if (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) { + return [this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : "layout"]; + } const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : ""; const links = () => DocListCast(this.doc.links).length ? "links" : ""; - const data = () => this.childDocs && !this.props.treeView.dashboardMode ? this.fieldKey : ""; + const data = () => this.childDocs ? this.fieldKey : ""; const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases"; const fields = () => Doc.UserDoc().noviceMode ? "" : "fields"; - return [data(), "layout", ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m); + const layout = this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; + return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m); } @action expandNextviewType = () => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 37444a9dc..b3c57d33a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -7,6 +7,7 @@ import { Cast, NumCast, StrCast } from "../../../../fields/Types"; import { aggregateBounds } from "../../../../Utils"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import React = require("react"); +import { ColorScheme } from "../../../util/SettingsManager"; export interface ViewDefBounds { type: string; @@ -361,7 +362,7 @@ export function computeTimelineLayout( groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined }); } - const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; + const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]); function layoutDocsAtTime(keyDocs: Doc[], key: number) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 94cf1c5a6..0b12f6c21 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -51,6 +51,7 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import Color = require("color"); +import { ColorScheme } from "../../../util/SettingsManager"; export const panZoomSchema = createSchema({ _panX: "number", @@ -1419,6 +1420,7 @@ export class CollectionFreeFormView extends CollectionSubView { const ctx = el?.getContext('2d'); @@ -1429,7 +1431,7 @@ export class CollectionFreeFormView extends CollectionSubView 50 ? [3, 3] : [1, 5]); ctx.clearRect(0, 0, w, h); if (ctx) { - ctx.strokeStyle = "rgba(0, 0, 0, 0.5)"; + ctx.strokeStyle = strokeStyle; ctx.beginPath(); for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { ctx.moveTo(x, Cy - h); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index dfe99ffc8..12493ecc1 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -337,8 +337,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { {this.renderTypes(this._col)} {this.renderColors(this._col)}
    - +
    ; } @@ -353,7 +354,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action setFocused = (doc: Doc) => this._focusedTable = doc; @action setPreviewDoc = (doc: Opt) => { - SelectionManager.SelectSchemaView(this, doc); + SelectionManager.SelectSchemaViewDoc(doc); this._previewDoc = doc; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3e15ed661..5d0b91b91 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -25,7 +25,6 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { LinkManager } from '../../util/LinkManager'; import { Scripting } from '../../util/Scripting'; import { SelectionManager } from "../../util/SelectionManager"; -import { ColorScheme } from "../../util/SettingsManager"; import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from "../../util/Transform"; @@ -50,6 +49,7 @@ import { ScriptingBox } from "./ScriptingBox"; import { PresBox } from './trails/PresBox'; import React = require("react"); import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import { ColorScheme } from "../../util/SettingsManager"; const { Howl } = require('howler'); interface Window { @@ -1061,9 +1061,7 @@ export class DocumentViewInternal extends DocComponent(Fon render() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const dark: boolean = Doc.UserDoc().colorScheme === ColorScheme.Dark; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
    diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index e519de1c5..149836e93 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -12,6 +12,7 @@ import { FormattedTextBox } from "./FormattedTextBox"; import React = require("react"); import * as ReactDOM from 'react-dom'; import { observer } from "mobx-react"; +import { ColorScheme } from "../../../util/SettingsManager"; export class DashDocView { _fieldWrapper: HTMLSpanElement; // container for label and value @@ -20,7 +21,7 @@ export class DashDocView { this._fieldWrapper = document.createElement("span"); this._fieldWrapper.style.position = "relative"; this._fieldWrapper.style.textIndent = "0"; - this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard.darkScheme ? "dimGray" : "lightGray")); + this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "lightGray")); this._fieldWrapper.style.width = node.attrs.width; this._fieldWrapper.style.height = node.attrs.height; this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block"; diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 404e828ea..652804126 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -17,7 +17,7 @@ import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents'; import { DocumentType } from "../client/documents/DocumentTypes"; import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; import { Scripting } from '../client/util/Scripting'; -import { SettingsManager } from '../client/util/SettingsManager'; +import { SettingsManager, ColorScheme } from '../client/util/SettingsManager'; import { Transform } from '../client/util/Transform'; import { UndoManager } from "../client/util/UndoManager"; import { TabDocView } from '../client/views/collections/TabDocView'; @@ -403,7 +403,7 @@ export class MobileInterface extends React.Component { const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); - const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`); + const toggleTheme = ScriptField.MakeScript(`self.colorScheme = self.colorScheme ? undefined: ${ColorScheme.Dark}}`); const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); const cloneDashboard = ScriptField.MakeScript(`cloneDashboard()`); dashboardDoc.contextMenuScripts = new List([toggleTheme!, toggleComic!, cloneDashboard!]); -- cgit v1.2.3-70-g09d2 From b290b0c33d13df5ac574ad5c6693586cf72cac6b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 21 Sep 2021 19:59:30 -0400 Subject: temporary change to reset old accounts on browndash to enable dark mode. --- src/client/util/CurrentUserUtils.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 568a9ddbd..2fcf06bd8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1363,6 +1363,9 @@ export class CurrentUserUtils { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } + if (doc.activeCollectionNestedBackground === "white") { // temporary to avoid having to rebuild the databse for old accounts that have this set by default. + doc.activeCollectionNestedBackground = undefined; + } return doc; } -- cgit v1.2.3-70-g09d2 From 80e5aed7d9cff03b602c03ac3655b2f17d91574b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 21 Sep 2021 20:00:22 -0400 Subject: from last --- src/client/util/CurrentUserUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2fcf06bd8..5683a8c21 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1363,8 +1363,8 @@ export class CurrentUserUtils { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } - if (doc.activeCollectionNestedBackground === "white") { // temporary to avoid having to rebuild the databse for old accounts that have this set by default. - doc.activeCollectionNestedBackground = undefined; + if (doc.activeCollectionBackground === "white") { // temporary to avoid having to rebuild the databse for old accounts that have this set by default. + doc.activeCollectionBackground = undefined; } return doc; } -- cgit v1.2.3-70-g09d2 From 12ae56962397a786397158af9e442a955883d16f Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Sep 2021 03:30:58 -0400 Subject: fixed runtime bug inside toggleBold(). removed print statements. --- src/client/documents/Documents.ts | 3 +- src/client/util/CurrentUserUtils.ts | 3 +- src/client/views/MainView.tsx | 1 - .../collectionLinear/CollectionLinearView.tsx | 1 - src/client/views/nodes/button/FontIconBox.tsx | 126 +++++--------- .../views/nodes/formattedText/FormattedTextBox.tsx | 11 +- .../views/nodes/formattedText/RichTextMenu.tsx | 186 +++++++++++---------- src/client/views/nodes/formattedText/marks_rts.ts | 6 +- 8 files changed, 153 insertions(+), 184 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 206f65bfd..8ac647b99 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -141,8 +141,8 @@ export class DocumentOptions { _columnWidth?: number; _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden _fontSize?: string; - _fontWeight?: number; _fontFamily?: string; + _fontWeight?: string; _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views _curPage?: number; // current page of a PDF or other? paginated document _currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds @@ -1188,7 +1188,6 @@ export namespace DocUtils { description: ":" + StrCast(note.title), event: undoBatch((args: { x: number, y: number }) => { const textDoc = Docs.Create.TextDocument("", { - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), _width: 200, x, y, _autoHeight: note._autoHeight !== false, title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) }); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 5683a8c21..297d4b241 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1535,8 +1535,7 @@ export class CurrentUserUtils { public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { const tbox = Docs.Create.TextDocument("", { _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor, - _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize), - _fontFamily: StrCast(Doc.UserDoc().fontFamily), title + _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, title }); const template = Doc.UserDoc().defaultTextLayout; if (template instanceof Doc) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index c99ba447c..fbd3fece2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -471,7 +471,6 @@ export class MainView extends React.Component { this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250); this._sidebarContent.proto = button.target as any; this.LastButton = button; - console.log(button.title); }); closeFlyout = action(() => { diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 7fe95fef0..18a715edf 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -109,7 +109,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { } myContextMenu = (e: React.MouseEvent) => { - console.log("STOPPING"); e.stopPropagation(); e.preventDefault(); } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 520034c3c..6a782b105 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -113,7 +113,7 @@ export class FontIconBox extends DocComponent(Fon // Script for checking the outcome of the toggle const checkScript: string = StrCast(this.rootDoc.script) + "(0, true)"; - const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result; + const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result || 0; if (numBtnType === NumButtonType.Slider) { @@ -158,7 +158,7 @@ export class FontIconBox extends DocComponent(Fon
    -
    setValue(checkResult - 1))}> +
    setValue(Number(checkResult) - 1))}>
    (Fon onChange={action((e) => setValue(Number(e.target.value)))} />
    -
    setValue(checkResult + 1))}> +
    setValue(Number(checkResult) + 1))}>
    @@ -261,8 +261,8 @@ export class FontIconBox extends DocComponent(Fon } noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; } else if (script === 'setFont') { - const selected = SelectionManager.Docs().lastElement(); - text = StrCast((selected?.type === DocumentType.RTF ? selected : Doc.UserDoc())._fontFamily); + const editorView = RichTextMenu.Instance?.TextView?.EditorView; + text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; } @@ -337,7 +337,7 @@ export class FontIconBox extends DocComponent(Fon const colorBox = (func: (color: ColorState) => void) => ; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
    @@ -360,7 +360,7 @@ export class FontIconBox extends DocComponent(Fon onClick={() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen} onPointerDown={e => e.stopPropagation()}> -
    +
    {label} {/* {dropdownCaret} */} {this.rootDoc.dropDownOpen ? @@ -387,7 +387,7 @@ export class FontIconBox extends DocComponent(Fon // Button label const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
    +
    {this.label}
    ; @@ -406,7 +406,7 @@ export class FontIconBox extends DocComponent(Fon } else { return (
    + style={{ opacity: 1, backgroundColor, color }}> {label}
    @@ -571,11 +571,7 @@ Scripting.addGlobal(function setView(view: string) { Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); if (checkResult) { - if (selected) { - return selected._backgroundColor; - } else { - return "#FFFFFF"; - } + return selected?._backgroundColor ?? "transparent"; } if (selected) selected._backgroundColor = color; Doc.UserDoc()._fontColor = color; @@ -596,7 +592,7 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; if (checkResult && selected) { if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE; - else return "transparent"; + return "transparent"; } selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed"); }); @@ -617,16 +613,18 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { Scripting.addGlobal(function setFont(font: string, checkResult?: boolean) { SelectionManager.Docs().map(doc => doc._fontFamily = font); const editorView = RichTextMenu.Instance.TextView?.EditorView; - editorView?.state && RichTextMenu.Instance.setFontFamily(font, editorView); - Doc.UserDoc()._fontFamily = font; - return Doc.UserDoc()._fontFamily; + if (checkResult) { + return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + } + if (editorView) RichTextMenu.Instance.setFontFamily(font); + else Doc.UserDoc().fontFamily = font; }); Scripting.addGlobal(function getActiveTextInfo(info: "family" | "size" | "color" | "highlight") { const editorView = RichTextMenu.Instance.TextView?.EditorView; const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection(); switch (info) { - case "family": return style?.activeColors[0]; + case "family": return style?.activeFamilies[0]; case "size": return style?.activeSizes[0]; case "color": return style?.activeColors[0]; case "highlight": return style?.activeHighlights[0]; @@ -643,7 +641,7 @@ Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", ch active = StrCast(Doc.UserDoc().textAlign); } if (active === align) return Colors.MEDIUM_BLUE; - else return "transparent"; + return "transparent"; } SelectionManager.Docs().map(doc => doc.textAlign = align); switch (align) { @@ -667,38 +665,25 @@ Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", check if (checkResult) { const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle(); if (active === mapStyle) return Colors.MEDIUM_BLUE; - else return "transparent"; + return "transparent"; } if (editorView) { const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle(); - if (active === mapStyle) { - editorView?.state && RichTextMenu.Instance.changeListType(editorView.state.schema.nodes.ordered_list.create({ mapStyle: "" })); - } else { - editorView?.state && RichTextMenu.Instance.changeListType(editorView.state.schema.nodes.ordered_list.create({ mapStyle: mapStyle })); - } + editorView?.state && RichTextMenu.Instance.changeListType( + editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle })); } }); // toggle: Set overlay status of selected document Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { - if (selected) { - return selected._fontColor; - } else { - return Doc.UserDoc()._fontColor; - } + return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor; } - if (selected) { - selected._fontColor = color; - if (color) { - editorView?.state && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch); - } - } - Doc.UserDoc()._fontColor = color; + if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch); + else Doc.UserDoc()._fontColor = color; }); // toggle: Set overlay status of selected document @@ -707,11 +692,7 @@ Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: bool const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { - if (selected) { - return selected._fontHighlight; - } else { - return Doc.UserDoc()._fontHighlight; - } + return (selected ?? Doc.UserDoc())._fontHighlight; } if (selected) { selected._fontColor = color; @@ -722,62 +703,43 @@ Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: bool Doc.UserDoc()._fontHighlight = color; }); - - // toggle: Set overlay status of selected document -Scripting.addGlobal(function setFontSize(size: string, checkResult?: boolean) { +Scripting.addGlobal(function setFontSize(size: string | number, checkResult?: boolean) { + if (typeof size === "number") size = size.toString(); + if (size && Number(size).toString() === size) size += "px"; + const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { - const size: number = parseInt(StrCast(Doc.UserDoc()._fontSize), 10); - return size; + return (editorView ? RichTextMenu.Instance.fontSize : StrCast(Doc.UserDoc().fontSize, "10px")).replace("px", ""); } - const editorView = RichTextMenu.Instance.TextView?.EditorView; - editorView?.state && RichTextMenu.Instance.setFontSize(Number(size), editorView); - Doc.UserDoc()._fontSize = size + "px"; + if (editorView) RichTextMenu.Instance.setFontSize(size); + else Doc.UserDoc()._fontSize = size; }); Scripting.addGlobal(function toggleBold(checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (editorView) { - const bold: boolean = editorView?.state && RichTextMenu.Instance.getBoldStatus(); - if (bold) return Colors.MEDIUM_BLUE; - else return "transparent"; - } - else return "transparent"; + return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === "bold") ? Colors.MEDIUM_BLUE : "transparent"; } - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (editorView) { - editorView?.state && RichTextMenu.Instance.toggleBold(editorView, true); - } - SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.bold = !doc.bold); - Doc.UserDoc().bold = !Doc.UserDoc().bold; - return Doc.UserDoc().bold; + if (editorView) RichTextMenu.Instance?.toggleBold(); + else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === "bold" ? undefined : "bold"; }); Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - return "transparent"; - } - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (editorView) { - editorView?.state && RichTextMenu.Instance.toggleUnderline(editorView, true); + return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === "underline") ? Colors.MEDIUM_BLUE : "transparent"; } - SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.underline = !doc.underline); - Doc.UserDoc().underline = !Doc.UserDoc().underline; - return Doc.UserDoc().underline; + if (editorView) RichTextMenu.Instance?.toggleUnderline(); + else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === "underline" ? undefined : "underline"; }); Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - return "transparent"; - } - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (editorView) { - editorView?.state && RichTextMenu.Instance.toggleItalic(editorView, true); + return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === "italics") ? Colors.MEDIUM_BLUE : "transparent"; } - SelectionManager.Docs().filter(doc => StrCast(doc.type) === DocumentType.RTF).map(doc => doc.italic = !doc.italic); - Doc.UserDoc().italic = !Doc.UserDoc().italic; - return Doc.UserDoc().italic; + if (editorView) RichTextMenu.Instance?.toggleItalics(); + else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics"; }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 617fa0bee..16aa4de6c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1168,7 +1168,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selectOnLoad && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) { - this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })]; + this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), + schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), + ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), + ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []), + ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []), + ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: StrCast(Doc.UserDoc().fontFamily) })] : []), + ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: StrCast(Doc.UserDoc().fontSize, "") })] : []), + ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])]; } } @@ -1596,7 +1603,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), - fontWeight: Cast(this.layoutDoc._fontWeight, "number", null), + fontWeight: Cast(this.layoutDoc._fontWeight, "string", null) as any, fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), pointerEvents: interactive ? undefined : "none", }} diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 9904a7939..23a3d80a4 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,8 +1,7 @@ import React = require("react"); -import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; -import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx"; import { observer } from "mobx-react"; import { lift, wrapIn } from "prosemirror-commands"; import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model"; @@ -10,10 +9,7 @@ import { wrapInList } from "prosemirror-schema-list"; import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../../fields/Doc"; -import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField'; import { Cast, StrCast } from "../../../../fields/Types"; -import { TraceMobx } from "../../../../fields/util"; -import { unimplementedFunction, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { LinkManager } from "../../../util/LinkManager"; import { SelectionManager } from "../../../util/SelectionManager"; @@ -29,7 +25,7 @@ const { toggleMark } = require("prosemirror-commands"); @observer export class RichTextMenu extends AntimodeMenu { - static Instance: RichTextMenu; + @observable static Instance: RichTextMenu; public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable private _linkToRef = React.createRef(); @@ -39,22 +35,22 @@ export class RichTextMenu extends AntimodeMenu { public _brushMap: Map> = new Map(); @observable private collapsed: boolean = false; - @observable private boldActive: boolean = false; - @observable private italicsActive: boolean = false; - @observable private underlineActive: boolean = false; - @observable private strikethroughActive: boolean = false; - @observable private subscriptActive: boolean = false; - @observable private superscriptActive: boolean = false; - - @observable private activeFontSize: string = ""; - @observable private activeFontFamily: string = ""; + @observable private _boldActive: boolean = false; + @observable private _italicsActive: boolean = false; + @observable private _underlineActive: boolean = false; + @observable private _strikethroughActive: boolean = false; + @observable private _subscriptActive: boolean = false; + @observable private _superscriptActive: boolean = false; + + @observable private _activeFontSize: string = "13px"; + @observable private _activeFontFamily: string = ""; @observable private activeListType: string = ""; @observable private activeAlignment: string = "left"; @observable private brushMarks: Set = new Set(); @observable private showBrushDropdown: boolean = false; - @observable private activeFontColor: string = "black"; + @observable private _activeFontColor: string = "black"; @observable private showColorDropdown: boolean = false; @observable private activeHighlightColor: string = "transparent"; @@ -67,10 +63,12 @@ export class RichTextMenu extends AntimodeMenu { _delayHide = false; constructor(props: Readonly<{}>) { super(props); - RichTextMenu.Instance = this; - this._canFade = false; - //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); - runInAction(() => this.Pinned = true); + runInAction(() => { + RichTextMenu.Instance = this; + this._canFade = false; + //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); + this.Pinned = true; + }); } componentDidMount() { @@ -81,6 +79,14 @@ export class RichTextMenu extends AntimodeMenu { this._reaction?.(); } + @computed get bold() { return this._boldActive; } + @computed get underline() { return this._underlineActive; } + @computed get italics() { return this._italicsActive; } + @computed get strikeThrough() { return this._strikethroughActive; } + @computed get fontColor() { return this._activeFontColor; } + @computed get fontFamily() { return this._activeFontFamily; } + @computed get fontSize() { return this._activeFontSize; } + public delayHide = () => this._delayHide = true; @action @@ -110,10 +116,10 @@ export class RichTextMenu extends AntimodeMenu { this.activeListType = this.getActiveListStyle(); this.activeAlignment = this.getActiveAlignment(); - this.activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; - this.activeFontSize = !activeSizes.length ? "13px" : activeSizes.length === 1 ? String(activeSizes[0]) : "..."; - this.activeFontColor = !activeColors.length ? "black" : activeColors.length === 1 ? String(activeColors[0]) : "..."; - this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length === 1 ? String(activeHighlights[0]) : "..."; + this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; + this._activeFontSize = !activeSizes.length ? "13px" : activeSizes[0]; + this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "..."; + this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "..."; // update link in current selection this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle)); @@ -125,7 +131,7 @@ export class RichTextMenu extends AntimodeMenu { if (node?.type === schema.nodes.ordered_list) { let attrs = node.attrs; if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family }; - if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: `${mark.attrs.fontSize}px` }; + if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize }; if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color }; const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); @@ -142,17 +148,6 @@ export class RichTextMenu extends AntimodeMenu { } } - getBoldStatus() { - if (this.view && this.TextView.props.isSelected(true)) { - const path = (this.view.state.selection.$from as any).path; - for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { - if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { - return path[i].attrs.strong; - } - } - } - } - // finds font sizes and families in selection getActiveAlignment() { if (this.view && this.TextView.props.isSelected(true)) { @@ -193,25 +188,23 @@ export class RichTextMenu extends AntimodeMenu { if (this.TextView.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; - const ref_node = this.reference_node(pos); - if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { - const marks = Array.from(ref_node.marks); - marks.push(...(this.view.state.storedMarks as any)); - marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); - m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color); - m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "px"); - m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight)); + const marks: Mark[] = []; + if (state.selection.empty) { + const ref_node = this.reference_node(pos); + marks.push(...[...(ref_node !== this.view.state.doc && ref_node?.isText ? + [...(state.storedMarks ?? []), ...Array.from(ref_node.marks)] : [])]); + } else { + state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark)); }); } - !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily)))); - !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize)))); - !activeColors.length && (activeColors.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor)))); + marks.forEach(m => { + m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); + m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color); + m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize); + m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight)); + }); } - !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily))); - !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize))); - !activeColors.length && (activeColors.push(StrCast(Doc.UserDoc().fontColor, "black"))); - !activeHighlights.length && (activeHighlights.push(StrCast(Doc.UserDoc().fontHighlight, ""))); return { activeFamilies, activeSizes, activeColors, activeHighlights }; } @@ -251,11 +244,12 @@ export class RichTextMenu extends AntimodeMenu { return []; } activeMarks = markGroup.filter(mark_type => { - if (mark_type === state.schema.marks.pFontSize) { - return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); - } + // if (mark_type === state.schema.marks.pFontSize) { + // return mark.isINSet + // ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); + // } const mark = state.schema.mark(mark_type); - return ref_node.marks.includes(mark); + return mark.isInSet(ref_node.marks); }); } } @@ -270,56 +264,66 @@ export class RichTextMenu extends AntimodeMenu { setActiveMarkButtons(activeMarks: MarkType[] | undefined) { if (!activeMarks) return; - this.boldActive = false; - this.italicsActive = false; - this.underlineActive = false; - this.strikethroughActive = false; - this.subscriptActive = false; - this.superscriptActive = false; + this._boldActive = false; + this._italicsActive = false; + this._underlineActive = false; + this._strikethroughActive = false; + this._subscriptActive = false; + this._superscriptActive = false; activeMarks.forEach(mark => { switch (mark.name) { - case "strong": this.boldActive = true; break; - case "em": this.italicsActive = true; break; - case "underline": this.underlineActive = true; break; - case "strikethrough": this.strikethroughActive = true; break; - case "subscript": this.subscriptActive = true; break; - case "superscript": this.superscriptActive = true; break; + case "strong": this._boldActive = true; break; + case "em": this._italicsActive = true; break; + case "underline": this._underlineActive = true; break; + case "strikethrough": this._strikethroughActive = true; break; + case "subscript": this._subscriptActive = true; break; + case "superscript": this._superscriptActive = true; break; } }); } - toggleBold = (view: EditorView, forceBool?: boolean) => { - const mark = view.state.schema.mark(view.state.schema.marks.strong, { strong: forceBool }); - this.setMark(mark, view.state, view.dispatch, false); - view.focus(); + toggleBold = () => { + if (this.view) { + const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); + this.setMark(mark, this.view.state, this.view.dispatch, false); + this.view.focus(); + } } - toggleUnderline = (view: EditorView, forceBool?: boolean) => { - const mark = view.state.schema.mark(view.state.schema.marks.underline, { underline: forceBool }); - this.setMark(mark, view.state, view.dispatch, false); - view.focus(); + toggleUnderline = () => { + if (this.view) { + const mark = this.view.state.schema.mark(this.view.state.schema.marks.underline); + this.setMark(mark, this.view.state, this.view.dispatch, false); + this.view.focus(); + } } - toggleItalic = (view: EditorView, forceBool?: boolean) => { - const mark = view.state.schema.mark(view.state.schema.marks.em, { em: forceBool }); - this.setMark(mark, view.state, view.dispatch, false); - view.focus(); + toggleItalics = () => { + if (this.view) { + const mark = this.view.state.schema.mark(this.view.state.schema.marks.em); + this.setMark(mark, this.view.state, this.view.dispatch, false); + this.view.focus(); + } } - setFontSize = (size: number, view: EditorView) => { - const fmark = view.state.schema.marks.pFontSize.create({ fontSize: size }); - this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true); - view.focus(); - this.updateMenu(view, undefined, this.props); + setFontSize = (fontSize: string) => { + if (this.view) { + const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize }); + this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); + this.view.focus(); + this.updateMenu(this.view, undefined, this.props); + } } - setFontFamily = (family: string, view: EditorView) => { - const fmark = view.state.schema.marks.pFontFamily.create({ family: family }); - this.setMark(fmark, view.state, (tx: any) => view.dispatch(tx.addStoredMark(fmark)), true); - view.focus(); - this.updateMenu(view, undefined, this.props); + setFontFamily = (family: string) => { + if (this.view) { + const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family }); + this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); + this.view.focus(); + this.updateMenu(this.view, undefined, this.props); + } } setHighlight(color: String, view: EditorView, dispatch: any) { @@ -330,7 +334,7 @@ export class RichTextMenu extends AntimodeMenu { } setColor(color: String, view: EditorView, dispatch: any) { - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); + const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color }); if (view.state.selection.empty) { dispatch(view.state.tr.addStoredMark(colorMark)); return false; diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 655ee7e44..6103a28d6 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -61,13 +61,13 @@ export const marks: { [index: string]: MarkSpec } = { /** FONT SIZES */ pFontSize: { - attrs: { fontSize: { default: 10 } }, + attrs: { fontSize: { default: "10px" } }, parseDOM: [{ tag: "span", getAttrs(dom: any) { - return { fontSize: dom.style.fontSize ? Number(dom.style.fontSize.replace("px", "")) : "" }; + return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : "" }; } }], - toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize}px;` }] : ['span', 0] + toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0] }, /* FONTS */ -- cgit v1.2.3-70-g09d2 From cbd8f931480ecfdc9c22cd2daa633e9b80b5a6e2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Sep 2021 04:46:24 -0400 Subject: fromlast --- src/client/views/nodes/formattedText/RichTextMenu.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 23a3d80a4..0a2a54aae 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -334,13 +334,11 @@ export class RichTextMenu extends AntimodeMenu { } setColor(color: String, view: EditorView, dispatch: any) { - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color }); - if (view.state.selection.empty) { - dispatch(view.state.tr.addStoredMark(colorMark)); - return false; + if (this.view) { + const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color }); + view.focus(); + this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true); } - this.setMark(colorMark, view.state, dispatch, true); - view.focus(); } // TODO: remove doesn't work -- cgit v1.2.3-70-g09d2 From 5e550b24c562a0fc35a734e99281a8f823ecbf15 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Sep 2021 05:07:25 -0400 Subject: fixes for fontcolor dropdown to be continuous as pointer moves. --- src/client/views/nodes/button/FontIconBox.tsx | 43 +++++++++++----------- .../views/nodes/formattedText/RichTextMenu.tsx | 3 +- 2 files changed, 24 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 6a782b105..368699dea 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -318,27 +318,33 @@ export class FontIconBox extends DocComponent(Fon ); } + @computed get colorScript() { + const script = StrCast(this.rootDoc.script); + return ScriptField.MakeScript(script + '(colValue, checkResult)', { colValue: "string", checkResult: "boolean" }); + } + colorPicker = (boolResult: string) => { + const click = (value: ColorState) => { + const s = this.colorScript; + s && undoBatch(() => s.script.run({ colValue: Utils.colorString(value), checkResult: false }).result)(); + }; + const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', + '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', + '#FFFFFF', '#f1efeb', "transparent"]; + return ; + } /** * Color button */ @computed get colorButton() { - const active: string = StrCast(this.rootDoc.dropDownOpen); + const active = StrCast(this.rootDoc.dropDownOpen); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const boolResult = this.colorScript?.script.run({ colValue: undefined, checkResult: true }).result; - const script: string = StrCast(this.rootDoc.script); - const scriptCheck: string = script + "(undefined, true)"; - const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result; - - const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', - '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', - '#FFFFFF', '#f1efeb', "transparent"]; - - const colorBox = (func: (color: ColorState) => void) => ; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) :
    {this.label} @@ -350,10 +356,6 @@ export class FontIconBox extends DocComponent(Fon
    ; - const click = (value: ColorState) => { - const s = ScriptField.MakeScript(script + '("' + Utils.colorString(value) + '", false)'); - s && undoBatch(() => s.script.run().result)(); - }; return (
    (Fon
    e.stopPropagation()} onClick={e => e.stopPropagation()}> - {colorBox(click)} + {this.colorPicker(boolResult ?? "transparent")}
    { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} />
    @@ -574,7 +576,6 @@ Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: bo return selected?._backgroundColor ?? "transparent"; } if (selected) selected._backgroundColor = color; - Doc.UserDoc()._fontColor = color; }); // toggle: Set overlay status of selected document @@ -683,7 +684,7 @@ Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) } if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch); - else Doc.UserDoc()._fontColor = color; + else Doc.UserDoc().fontColor = color; }); // toggle: Set overlay status of selected document diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 0a2a54aae..3efc46259 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -336,8 +336,9 @@ export class RichTextMenu extends AntimodeMenu { setColor(color: String, view: EditorView, dispatch: any) { if (this.view) { const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color }); - view.focus(); this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true); + view.focus(); + this.updateMenu(this.view, undefined, this.props); } } -- cgit v1.2.3-70-g09d2 From 13ec8c89d7fb809a9ceefa3c25e332b0f4457593 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Sep 2021 11:28:46 -0400 Subject: fixed dropdowns to be over documentdecorations by moving top menu bar out of maincontent area and giving it a higher z-index than DocDecorations. fixed toggle behavior of shape drawing modes. made color dropdown state be runtime only (doesn't write to data model) -- need to decide if that's what we want. --- src/client/views/MainView.tsx | 14 +++--- src/client/views/nodes/button/FontIconBox.tsx | 68 +++++++++++++-------------- 2 files changed, 42 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index fbd3fece2..35c5801e5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -12,7 +12,7 @@ import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; -import { BoolCast, PromiseValue, StrCast } from '../../fields/Types'; +import { PromiseValue, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; @@ -26,7 +26,7 @@ import { HistoryUtil } from '../util/History'; import { Hypothesis } from '../util/HypothesisUtils'; import { Scripting } from '../util/Scripting'; import { SelectionManager } from '../util/SelectionManager'; -import { SettingsManager, ColorScheme } from '../util/SettingsManager'; +import { ColorScheme, SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; @@ -101,7 +101,7 @@ export class MainView extends React.Component { propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0)); propertiesHeight = () => this._dashUIHeight; mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth(); - mainDocViewHeight = () => this._dashUIHeight - this.topMenuHeight(); + mainDocViewHeight = () => this._dashUIHeight; componentDidMount() { document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!)); @@ -436,7 +436,6 @@ export class MainView extends React.Component {
    - {this.dockingContent} @@ -460,7 +459,7 @@ export class MainView extends React.Component { })).observe(r); }} style={{ color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black", - height: `calc(100% - ${this.topOfDashUI}px)`, + height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`, width: "100%", }} > {this.mainInnerContent} @@ -613,11 +612,14 @@ export class MainView extends React.Component { - + {this.topbar} {LinkDescriptionPopup.descriptionPopup ? : null} {DocumentLinksButton.LinkEditorDocView ? : (null)} {LinkDocPreview.LinkInfo ? : (null)} +
    + +
    {this.mainDashboardArea} diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 368699dea..511df8786 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -5,7 +5,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorState, SketchPicker } from 'react-color'; -import { Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc, StrListCast, WidthSym, HeightSym } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { createSchema, makeInterface } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; @@ -318,35 +318,35 @@ export class FontIconBox extends DocComponent(Fon ); } + @observable colorPickerClosed: boolean = true; @computed get colorScript() { const script = StrCast(this.rootDoc.script); return ScriptField.MakeScript(script + '(colValue, checkResult)', { colValue: "string", checkResult: "boolean" }); } - colorPicker = (boolResult: string) => { - const click = (value: ColorState) => { + + colorPicker = (curColor: string) => { + const change = (value: ColorState) => { const s = this.colorScript; s && undoBatch(() => s.script.run({ colValue: Utils.colorString(value), checkResult: false }).result)(); }; - const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', + const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', "transparent"]; return ; + onChange={change} + color={curColor} + presetColors={presets} /> } /** * Color button */ @computed get colorButton() { - const active = StrCast(this.rootDoc.dropDownOpen); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const boolResult = this.colorScript?.script.run({ colValue: undefined, checkResult: true }).result; + const curColor = this.colorScript?.script.run({ colValue: undefined, checkResult: true }).result ?? "transparent"; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
    +
    {this.label}
    ; @@ -355,26 +355,27 @@ export class FontIconBox extends DocComponent(Fon style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
    ; - + setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return ( -
    this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen} + onClick={action(() => this.colorPickerClosed = !this.colorPickerClosed)} onPointerDown={e => e.stopPropagation()}> -
    +
    {label} {/* {dropdownCaret} */} - {this.rootDoc.dropDownOpen ? + {this.colorPickerClosed ? (null) :
    e.stopPropagation()} onClick={e => e.stopPropagation()}> - {this.colorPicker(boolResult ?? "transparent")} + {this.colorPicker(curColor)}
    -
    { e.stopPropagation(); this.rootDoc.dropDownOpen = false; }} /> -
    - : null} +
    { + e.stopPropagation(); this.colorPickerClosed = true; + })} /> +
    }
    ); } @@ -754,24 +755,23 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolean) { if (checkResult) { - if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) return Colors.MEDIUM_BLUE; - else return "transparent"; + return (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance?.InkShape === "" || GestureOverlay.Instance?.InkShape === tool) ? + Colors.MEDIUM_BLUE : "transparent"; } - if (tool === "circle") { - Doc.UserDoc().activeInkTool = "pen"; - GestureOverlay.Instance.InkShape = tool; - } else if (tool === "square") { - Doc.UserDoc().activeInkTool = "pen"; - GestureOverlay.Instance.InkShape = tool; - } else if (tool === "line") { - Doc.UserDoc().activeInkTool = "pen"; - GestureOverlay.Instance.InkShape = tool; - } else if (tool) { - if (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance.InkShape === "" || GestureOverlay.Instance.InkShape === tool) { - GestureOverlay.Instance.InkShape = ""; + if (["circle", "square", "line"].includes(tool)) { + if (GestureOverlay.Instance.InkShape === tool) { + Doc.UserDoc().activeInkTool = InkTool.None; + GestureOverlay.Instance.InkShape = InkTool.None; + } else { + Doc.UserDoc().activeInkTool = InkTool.Pen; + GestureOverlay.Instance.InkShape = tool; + } + } else if (tool) { // pen + if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) { Doc.UserDoc().activeInkTool = InkTool.None; } else { Doc.UserDoc().activeInkTool = tool; + GestureOverlay.Instance.InkShape = ""; } } else { Doc.UserDoc().activeInkTool = InkTool.None; -- cgit v1.2.3-70-g09d2 From c5dff2f99eeee744c7be903a7127b283b8c7fbf8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Sep 2021 12:43:28 -0400 Subject: fixed problem with fontsize corruption. fixed showing correct font size when creating a new empty document with storedMarks. fixed color of previewcursor in dark mode. --- src/client/views/PreviewCursor.scss | 5 +++++ src/client/views/PreviewCursor.tsx | 4 ++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 9 +++++---- src/client/views/nodes/formattedText/RichTextMenu.tsx | 5 ++--- 4 files changed, 14 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss index de9bd69c4..60b7d14a0 100644 --- a/src/client/views/PreviewCursor.scss +++ b/src/client/views/PreviewCursor.scss @@ -1,4 +1,5 @@ +.previewCursor-Dark, .previewCursor { color: black; position: absolute; @@ -8,4 +9,8 @@ pointer-events: none; opacity: 1; z-index: 1001; +} + +.previewCursor-Dark { + color: white; } \ No newline at end of file diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 2b82ef475..ef1360ef1 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; import { Doc } from '../../fields/Doc'; -import { Cast, NumCast } from '../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../fields/Types'; import { DocServer } from '../DocServer'; import { Docs, DocUtils } from '../documents/Documents'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; @@ -158,7 +158,7 @@ export class PreviewCursor extends React.Component<{}> { } render() { return (!PreviewCursor._clickPoint || !PreviewCursor.Visible) ? (null) : -
    e?.focus()} +
    e?.focus()} style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}> I
    ; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 16aa4de6c..7afa54d5f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -279,8 +279,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (removeSelection(json) !== removeSelection(curLayout?.Data)) { - !curText && tx.storedMarks?.filter(m => m.type.name === "pFontSize").map(m => Doc.UserDoc().fontSize = this.layoutDoc._fontSize = (m.attrs.fontSize + "px")); - !curText && tx.storedMarks?.filter(m => m.type.name === "pFontFamily").map(m => Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily); this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); @@ -894,11 +892,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }); this._disposers.selected = reaction(() => this.props.isSelected(), - action((selected) => { + action(selected => { if (RichTextMenu.Instance?.view === this._editorView && !selected) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } - })); + if (this._editorView && selected) { + RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); + } + }), { fireImmediately: true }); if (!this.props.dontRegisterView) { this._disposers.record = reaction(() => this._recording, diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 3efc46259..bd05af977 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -188,11 +188,10 @@ export class RichTextMenu extends AntimodeMenu { if (this.TextView.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; - const marks: Mark[] = []; + const marks: Mark[] = [...(state.storedMarks ?? [])]; if (state.selection.empty) { const ref_node = this.reference_node(pos); - marks.push(...[...(ref_node !== this.view.state.doc && ref_node?.isText ? - [...(state.storedMarks ?? []), ...Array.from(ref_node.marks)] : [])]); + marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : [])); } else { state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark)); -- cgit v1.2.3-70-g09d2 From 0079b81fdd56e8aac4ded4c82ecb013fe06057a2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Sep 2021 12:47:27 -0400 Subject: fixed default color of link lines in dark mode. --- .../collections/collectionFreeForm/CollectionFreeFormLinkView.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 2cb487588..f6c2707da 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -3,9 +3,11 @@ import { observer } from "mobx-react"; import { Doc, Field } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { List } from "../../../../fields/List"; -import { NumCast, StrCast } from "../../../../fields/Types"; +import { NumCast } from "../../../../fields/Types"; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { LinkManager } from "../../../util/LinkManager"; +import { ColorScheme } from "../../../util/SettingsManager"; import { SnappingManager } from "../../../util/SnappingManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; @@ -184,7 +186,7 @@ export class CollectionFreeFormLinkView extends React.Component; const linkColorList = Doc.UserDoc().linkColorList as List; //access stroke color using index of the relationship in the color list (default black) - const strokeColor = linkRelationshipList.indexOf(linkRelationship) === -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; + const strokeColor = linkRelationshipList.indexOf(linkRelationship) === -1 ? (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "white" : "black") : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> -- cgit v1.2.3-70-g09d2 From d5f9533d153e11e24d2ab7c03b4561170f0768fe Mon Sep 17 00:00:00 2001 From: Geireann <60007097+geireann@users.noreply.github.com> Date: Thu, 23 Sep 2021 11:18:30 -0400 Subject: small schema UI tweak --- src/client/util/CurrentUserUtils.ts | 2 -- .../collections/collectionSchema/CollectionSchemaView.scss | 3 ++- src/client/views/nodes/button/FontIconBox.tsx | 13 +++++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 297d4b241..4ec83f2d7 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1019,8 +1019,6 @@ export class CurrentUserUtils { title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, - switchToggle: true, - width: 100, buttonText: "Show Preview", icon: "eye", click: 'toggleSchemaPreview()', diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 3074ce66e..9ebe14d6c 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -444,11 +444,12 @@ button.add-column { border: none; background-color: $white; width: 100%; - height: 100%; + height: fit-content; min-height: 26px; } } &.focused { + overflow: hidden; &.inactive { border: none; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 511df8786..af4a581f1 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -861,4 +861,17 @@ Scripting.addGlobal(function toggleSchemaPreview(checkResult?: boolean) { selected.schemaPreviewWidth = 0; } } +}); + +/** STACK + * groupBy + */ +Scripting.addGlobal(function setGroupBy(key: string, checkResult?: boolean) { + SelectionManager.Docs().map(doc => doc._fontFamily = key); + const editorView = RichTextMenu.Instance.TextView?.EditorView; + if (checkResult) { + return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + } + if (editorView) RichTextMenu.Instance.setFontFamily(key); + else Doc.UserDoc().fontFamily = key; }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 780346cb03a2dcc10c1edcf4ecc4f57a091d36bc Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 23 Sep 2021 15:26:30 -0400 Subject: fixed one crash when color string has capital letters. --- src/client/util/CurrentUserUtils.ts | 10 +++++----- .../collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | 2 +- src/client/views/collections/collectionSchema/SchemaTable.tsx | 2 +- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 7 +++---- src/client/views/nodes/LabelBox.scss | 2 +- src/client/views/nodes/LinkAnchorBox.tsx | 2 +- src/client/views/nodes/formattedText/DashDocView.tsx | 2 +- src/client/views/nodes/formattedText/DashFieldView.scss | 2 +- 9 files changed, 15 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 4ec83f2d7..3c32c2359 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -226,9 +226,9 @@ export class CurrentUserUtils { const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); descriptionWrapper._columnHeaders = new List([ - new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false), - new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true), - new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true), + new SchemaHeaderField("[A Short Description]", "dimgray", undefined, undefined, undefined, false), + new SchemaHeaderField("[Long Description]", "dimgray", undefined, undefined, undefined, true), + new SchemaHeaderField("[Details]", "dimgray", undefined, undefined, undefined, true), ]); const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, _chromeHidden: true, system: true }); detailView.isTemplateDoc = makeTemplate(detailView); @@ -349,7 +349,7 @@ export class CurrentUserUtils { static setupDefaultIconTemplates(doc: Doc) { if (doc["template-icon-view"] === undefined) { const iconView = Docs.Create.LabelDocument({ - title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimGray", + title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); // Docs.Create.TextDocument("", { @@ -1238,7 +1238,7 @@ export class CurrentUserUtils { static setupSearchSidebar(doc: Doc) { if (doc.mySearchPanel === undefined) { doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({ - backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true, + backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true, childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "Search Panel", system: true })) as any as Doc; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index b3c57d33a..9fed82dae 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -362,7 +362,7 @@ export function computeTimelineLayout( groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined }); } - const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; + const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]); function layoutDocsAtTime(keyDocs: Doc[], key: number) { diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index 3833f968b..d157832d9 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -567,7 +567,7 @@ export class SchemaTable extends React.Component {
    { this._timeout = undefined; clickFunc(); }, 350); } else clickFunc(); - } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself - this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right"); } else if (this.allLinks && this.Document.type !== DocumentType.LINK && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey); } else { @@ -582,8 +580,9 @@ export class DocumentViewInternal extends DocComponent Doc.UnBrushDoc(this.rootDoc)}> { })} /> {!this._forceOpen ? (null) :
    this._isOpen = this._editing = this._forceOpen = false)}> - +
    }
    ); diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 149836e93..364be461f 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -21,7 +21,7 @@ export class DashDocView { this._fieldWrapper = document.createElement("span"); this._fieldWrapper.style.position = "relative"; this._fieldWrapper.style.textIndent = "0"; - this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimGray" : "lightGray")); + this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "lightGray")); this._fieldWrapper.style.width = node.attrs.width; this._fieldWrapper.style.height = node.attrs.height; this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block"; diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss index e7dd286a5..c36e6804b 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.scss +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -8,7 +8,7 @@ height: 10px; position: relative; display: inline-block; - background: dimGray; + background: dimgray; } .dashFieldView-fieldCheck { min-width: 12px; -- cgit v1.2.3-70-g09d2 From 4e6a1d7a37c8c28014a9f7cd0d92f17c8f29454d Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 23 Sep 2021 19:30:52 +0000 Subject: Revert "Merge pull request #34 from brown-dash/linking-anh" This reverts commit 70d80e30de9963c353636d9780ffb83f3285aac7, reversing changes made to d5f9533d153e11e24d2ab7c03b4561170f0768fe. --- src/client/documents/Documents.ts | 1 - src/client/util/LinkManager.ts | 2 -- .../CollectionFreeFormLinkView.tsx | 15 +++------------ src/client/views/linking/LinkEditor.tsx | 20 ++------------------ src/client/views/linking/LinkMenuItem.tsx | 4 ++-- src/fields/Doc.ts | 1 - 6 files changed, 7 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6fca0b67e..8ac647b99 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -301,7 +301,6 @@ export class DocumentOptions { border?: string; //for searchbox hoverBackgroundColor?: string; // background color of a label when hovered linkRelationshipList?: List; // for storing different link relationships (when set by user in the link editor) - linkRelationshipSizes?: List; //stores number of links contained in each relationship linkColorList?: List; // colors of links corresponding to specific link relationships } export namespace Docs { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 4c24fbafb..64da68f59 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -103,10 +103,8 @@ export class LinkManager { if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) { const linkRelationshipList = new List(); const linkColorList = new List(); - const linkRelationshipSizes = new List(); Doc.UserDoc().linkRelationshipList = linkRelationshipList; Doc.UserDoc().linkColorList = linkColorList; - Doc.UserDoc().linkRelationshipSizes = linkRelationshipSizes; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 88cd0feb3..f6c2707da 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -42,7 +42,7 @@ export class CollectionFreeFormLinkView extends React.Component this._opacity = 0.75), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() + setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); @@ -180,24 +180,15 @@ export class CollectionFreeFormLinkView extends React.Component; const linkColorList = Doc.UserDoc().linkColorList as List; - const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List; - const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); - //access stroke color using index of the relationship in the color list (default black) - const strokeColor = currRelationshipIndex == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; - - //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) - //thickness varies linearly from 3px to 12px for increasing link count - const strokeWidth: string = currRelationshipIndex == -1 ? "3px" : Math.floor(2 + 10 * (linkRelationshipSizes[currRelationshipIndex] / Math.max(...linkRelationshipSizes))) + "px"; - + const strokeColor = linkRelationshipList.indexOf(linkRelationship) === -1 ? (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "white" : "black") : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - {textX === undefined ? (null) : {Field.toString(this.props.LinkDocs[0].description as any as Field)} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 58c57a23b..219f7d3a2 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, NumListCast, StrListCast, Field } from "../../../fields/Doc"; +import { Doc, StrListCast, Field } from "../../../fields/Doc"; import { DateCast, StrCast, Cast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; @@ -42,28 +42,14 @@ export class LinkEditor extends React.Component { @undoBatch setRelationshipValue = action((value: string) => { if (LinkManager.currentLink) { - const prevRelationship = LinkManager.currentLink.linkRelationship as string; - LinkManager.currentLink.linkRelationship = value; Doc.GetProto(LinkManager.currentLink).linkRelationship = value; const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); - const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().linkColorList); - // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color if (linkRelationshipList && !linkRelationshipList.includes(value)) { linkRelationshipList.push(value); - linkRelationshipSizes.push(1); const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; - linkColorList.push(randColor) - // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes - } else if (linkRelationshipList && value != prevRelationship) { - //increment size of new relationship size - linkRelationshipSizes[linkRelationshipList.indexOf(value)] = linkRelationshipSizes[linkRelationshipList.indexOf(value)] + 1; - //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) - if (linkRelationshipList.includes(prevRelationship)) { - linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] = linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] - 1; - } - + linkColorList.push(randColor); } this.relationshipButtonColor = "rgb(62, 133, 55)"; setTimeout(action(() => this.relationshipButtonColor = ""), 750); @@ -155,7 +141,6 @@ export class LinkEditor extends React.Component { style={{ width: "100%" }} id="input" value={this.relationship} - autoComplete={"off"} placeholder={"Enter link relationship"} onKeyDown={this.onRelationshipKey} onChange={this.handleRelationshipChange} @@ -184,7 +169,6 @@ export class LinkEditor extends React.Component {
    {
    {this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}
    }>
    e.stopPropagation()}> -
    +
    {!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}
    }>
    e.stopPropagation()}> -
    +
    {!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}
    }> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 5133da603..57bd0f46f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -80,7 +80,6 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { export async function DocCastAsync(field: FieldResult): Promise> { return Cast(field, Doc); } -export function NumListCast(field: FieldResult) { return Cast(field, listSpec("number"), []); } export function StrListCast(field: FieldResult) { return Cast(field, listSpec("string"), []); } export function DocListCast(field: FieldResult) { return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; } export function DocListCastOrNull(field: FieldResult) { return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; } -- cgit v1.2.3-70-g09d2 From 815899626d82972ae56918487ee3ece11446d5a6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 23 Sep 2021 20:22:58 -0400 Subject: minor fixes. --- src/Utils.ts | 2 +- src/client/views/StyleProvider.tsx | 17 ++++++++--------- src/client/views/nodes/button/FontIconBox.tsx | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index e11f1154e..db33875ea 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -576,7 +576,7 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe export function lightOrDark(color: any) { const nonAlphaColor = color.startsWith("#") ? (color as string).substring(0, 7) : color.startsWith("rgba") ? color.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : color; - const col = Color(nonAlphaColor).rgb(); + const col = Color(nonAlphaColor.toLowerCase()).rgb(); const colsum = (col.red() + col.green() + col.blue()); if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY; else return Colors.WHITE; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 1eb7a222e..507c3d2b2 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -1,28 +1,27 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Colors } from './global/globalEnums'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { runInAction, action } from 'mobx'; +import { action, runInAction } from 'mobx'; import { Doc, Opt, StrListCast } from "../../fields/Doc"; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; +import { lightOrDark } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import { ColorScheme } from '../util/SettingsManager'; import { SnappingManager } from '../util/SnappingManager'; -import { UndoManager, undoBatch } from '../util/UndoManager'; +import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionViewType } from './collections/CollectionView'; -import "./collections/TreeView.scss"; +import { Colors } from './global/globalEnums'; import { MainView } from './MainView'; import { DocumentViewProps } from "./nodes/DocumentView"; import { FieldViewProps } from './nodes/FieldView'; -import "./nodes/FilterBox.scss"; +import { SliderBox } from './nodes/SliderBox'; import "./StyleProvider.scss"; import React = require("react"); import Color = require('color'); -import { lightOrDark } from '../../Utils'; -import { ColorScheme } from '../util/SettingsManager'; export enum StyleLayers { Background = "background" @@ -136,7 +135,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt(Fon return + presetColors={presets} />; } /** * Color button -- cgit v1.2.3-70-g09d2 From 643df77e6366b7164307ffe195ed9de83b68e5ae Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 24 Sep 2021 01:21:38 -0400 Subject: wrapped Color() calls in DashColor() to catch exceptions for color strings no found --- src/Utils.ts | 11 ++++++++++- src/client/views/StyleProvider.tsx | 5 ++--- src/client/views/collections/TabDocView.tsx | 3 +-- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 1 - src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 5 ++--- src/client/views/nodes/trails/PresBox.tsx | 3 +-- src/fields/Doc.ts | 9 ++++----- src/fields/RichTextUtils.ts | 4 ++-- 8 files changed, 22 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index db33875ea..7ec4f69f3 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -573,10 +573,19 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe } } +export function DashColor(color: string) { + try { + return Color(color.toLowerCase()); + } catch (e) { + console.log("COLOR error:", e); + return Color("red"); + } +} + export function lightOrDark(color: any) { const nonAlphaColor = color.startsWith("#") ? (color as string).substring(0, 7) : color.startsWith("rgba") ? color.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : color; - const col = Color(nonAlphaColor.toLowerCase()).rgb(); + const col = DashColor(nonAlphaColor).rgb(); const colsum = (col.red() + col.green() + col.blue()); if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY; else return Colors.WHITE; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 507c3d2b2..cd6e11bda 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -7,7 +7,7 @@ import { Doc, Opt, StrListCast } from "../../fields/Doc"; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; -import { lightOrDark } from '../../Utils'; +import { lightOrDark, DashColor } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { ColorScheme } from '../util/SettingsManager'; @@ -21,7 +21,6 @@ import { FieldViewProps } from './nodes/FieldView'; import { SliderBox } from './nodes/SliderBox'; import "./StyleProvider.scss"; import React = require("react"); -import Color = require('color'); export enum StyleLayers { Background = "background" @@ -148,7 +147,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; @@ -165,7 +164,7 @@ export class CollectionFreeFormDocumentView extends DocComponent color !== "" && (Color(color).alpha() !== 1); + const isTransparent = (color: string) => color !== "" && (DashColor(color).alpha() !== 1); return isTransparent(StrCast(doc[key])); } if (typeof value === "string") { diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index 0b5f14d74..a19be5df9 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -2,7 +2,7 @@ import { AssertionError } from "assert"; import { docs_v1 } from "googleapis"; import { Fragment, Mark, Node } from "prosemirror-model"; import { sinkListItem } from "prosemirror-schema-list"; -import { Utils } from "../Utils"; +import { Utils, DashColor } from "../Utils"; import { Docs, DocUtils } from "../client/documents/Documents"; import { schema } from "../client/views/nodes/formattedText/schema_rts"; import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; @@ -482,7 +482,7 @@ export namespace RichTextUtils { } const fromHex = (color: string): docs_v1.Schema$OptionalColor => { - const c = Color(color); + const c = DashColor(color); return fromRgb.convert(c.red(), c.green(), c.blue()); }; -- cgit v1.2.3-70-g09d2 From b1b47b17955aa7fcb49d89c1bd538d4335202ed8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 24 Sep 2021 11:52:33 -0400 Subject: changed label boxes to fit their entire text by scaling their font --- src/client/views/nodes/FunctionPlotBox.tsx | 14 +- src/client/views/nodes/LabelBigText.js | 241 +++++++++++++++++++++++++++++ src/client/views/nodes/LabelBox.scss | 6 +- src/client/views/nodes/LabelBox.tsx | 28 +++- 4 files changed, 270 insertions(+), 19 deletions(-) create mode 100644 src/client/views/nodes/LabelBigText.js (limited to 'src') diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index b00f97236..5050fc2d2 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -1,18 +1,16 @@ -import EquationEditor from 'equation-editor-react'; import functionPlot from "function-plot"; +import { action, computed, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { Doc, DocListCast } from '../../../fields/Doc'; import { documentSchema } from '../../../fields/documentSchemas'; -import { createSchema, makeInterface, listSpec } from '../../../fields/Schema'; -import { StrCast, Cast } from '../../../fields/Types'; +import { List } from '../../../fields/List'; +import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { Cast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; +import { Docs } from '../../documents/Documents'; import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; -import './LabelBox.scss'; -import { DocListCast, Doc } from '../../../fields/Doc'; -import { computed, action, reaction } from 'mobx'; -import { Docs } from '../../documents/Documents'; -import { List } from '../../../fields/List'; const EquationSchema = createSchema({}); diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js new file mode 100644 index 000000000..db43551d8 --- /dev/null +++ b/src/client/views/nodes/LabelBigText.js @@ -0,0 +1,241 @@ +/* +Brorlandi/big-text.js v1.0.0, 2017 +Adapted from DanielHoffmann/jquery-bigtext, v1.3.0, May 2014 +And from Jetroid/bigtext.js v1.0.0, September 2016 + +Usage: +BigText("#myElement",{ + rotateText: {Number}, (null) + fontSizeFactor: {Number}, (0.8) + maximumFontSize: {Number}, (null) + limitingDimension: {String}, ("both") + horizontalAlign: {String}, ("center") + verticalAlign: {String}, ("center") + textAlign: {String}, ("center") + whiteSpace: {String}, ("nowrap") +}); + + +Original Projects: +https://github.com/DanielHoffmann/jquery-bigtext +https://github.com/Jetroid/bigtext.js + +Options: + +rotateText: Rotates the text inside the element by X degrees. + +fontSizeFactor: This option is used to give some vertical spacing for letters that overflow the line-height (like 'g', 'Á' and most other accentuated uppercase letters). This does not affect the font-size if the limiting factor is the width of the parent div. The default is 0.8 + +maximumFontSize: maximum font size to use. + +limitingDimension: In which dimension the font size should be limited. Possible values: "width", "height" or "both". Defaults to both. Using this option with values different than "both" overwrites the element parent width or height. + +horizontalAlign: Where to align the text horizontally. Possible values: "left", "center", "right". Defaults to "center". + +verticalAlign: Where to align the text vertically. Possible values: "top", "center", "bottom". Defaults to "center". + +textAlign: Sets the text align of the element. Possible values: "left", "center", "right". Defaults to "center". This option is only useful if there are linebreaks (
    tags) inside the text. + +whiteSpace: Sets whitespace handling. Possible values: "nowrap", "pre". Defaults to "nowrap". (Can also be set to enable wrapping but this doesn't work well.) + +Bruno Orlandi - 2017 + +Copyright (C) 2013 Daniel Hoffmann Bernardes, Ícaro Technologies +Copyright (C) 2016 Jet Holt + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +function _calculateInnerDimensions(computedStyle) { + //Calculate the inner width and height + var innerWidth; + var innerHeight; + + var width = parseInt(computedStyle.getPropertyValue("width")); + var height = parseInt(computedStyle.getPropertyValue("height")); + var paddingLeft = parseInt(computedStyle.getPropertyValue("padding-left")); + var paddingRight = parseInt(computedStyle.getPropertyValue("padding-right")); + var paddingTop = parseInt(computedStyle.getPropertyValue("padding-top")); + var paddingBottom = parseInt(computedStyle.getPropertyValue("padding-bottom")); + var borderLeft = parseInt(computedStyle.getPropertyValue("border-left-width")); + var borderRight = parseInt(computedStyle.getPropertyValue("border-right-width")); + var borderTop = parseInt(computedStyle.getPropertyValue("border-top-width")); + var borderBottom = parseInt(computedStyle.getPropertyValue("border-bottom-width")); + + //If box-sizing is border-box, we need to subtract padding and border. + var parentBoxSizing = computedStyle.getPropertyValue("box-sizing"); + if (parentBoxSizing == "border-box") { + innerWidth = width - (paddingLeft + paddingRight + borderLeft + borderRight); + innerHeight = height - (paddingTop + paddingBottom + borderTop + borderBottom); + } else { + innerWidth = width; + innerHeight = height; + } + var obj = {}; + obj["width"] = innerWidth; + obj["height"] = innerHeight; + return obj; +} + +export default function BigText(element, options) { + + if (typeof element === 'string') { + element = document.querySelector(element); + } else if (element.length) { + // Support for array based queries (such as jQuery) + element = element[0]; + } + + var defaultOptions = { + rotateText: null, + fontSizeFactor: 0.8, + maximumFontSize: null, + limitingDimension: "both", + horizontalAlign: "center", + verticalAlign: "center", + textAlign: "center", + whiteSpace: "nowrap" + }; + + //Merge provided options and default options + options = options || {}; + for (var opt in defaultOptions) + if (defaultOptions.hasOwnProperty(opt) && !options.hasOwnProperty(opt)) + options[opt] = defaultOptions[opt]; + + //Get variables which we will reference frequently + var style = element.style; + var computedStyle = document.defaultView.getComputedStyle(element); + var parent = element.parentNode; + var parentStyle = parent.style; + var parentComputedStyle = document.defaultView.getComputedStyle(parent); + + //hides the element to prevent "flashing" + style.visibility = "hidden"; + + //Set some properties + style.display = "inline-block"; + style.clear = "both"; + style.float = "left"; + style.fontSize = (1000 * options.fontSizeFactor) + "px"; + style.lineHeight = "1000px"; + style.whiteSpace = options.whiteSpace; + style.textAlign = options.textAlign; + style.position = "relative"; + style.padding = 0; + style.margin = 0; + style.left = "50%"; + style.top = "50%"; + + //Get properties of parent to allow easier referencing later. + var parentPadding = { + top: parseInt(parentComputedStyle.getPropertyValue("padding-top")), + right: parseInt(parentComputedStyle.getPropertyValue("padding-right")), + bottom: parseInt(parentComputedStyle.getPropertyValue("padding-bottom")), + left: parseInt(parentComputedStyle.getPropertyValue("padding-left")), + }; + var parentBorder = { + top: parseInt(parentComputedStyle.getPropertyValue("border-top")), + right: parseInt(parentComputedStyle.getPropertyValue("border-right")), + bottom: parseInt(parentComputedStyle.getPropertyValue("border-bottom")), + left: parseInt(parentComputedStyle.getPropertyValue("border-left")), + }; + + //Calculate the parent inner width and height + var parentInnerDimensions = _calculateInnerDimensions(parentComputedStyle); + var parentInnerWidth = parentInnerDimensions["width"]; + var parentInnerHeight = parentInnerDimensions["height"]; + + var box = { + width: element.offsetWidth, //Note: This is slightly larger than the jQuery version + height: element.offsetHeight, + }; + + + if (options.rotateText !== null) { + if (typeof options.rotateText !== "number") + throw "bigText error: rotateText value must be a number"; + var rotate = "rotate(" + options.rotateText + "deg)"; + style.webkitTransform = rotate; + style.msTransform = rotate; + style.MozTransform = rotate; + style.OTransform = rotate; + style.transform = rotate; + //calculating bounding box of the rotated element + var sine = Math.abs(Math.sin(options.rotateText * Math.PI / 180)); + var cosine = Math.abs(Math.cos(options.rotateText * Math.PI / 180)); + box.width = element.offsetWidth * cosine + element.offsetHeight * sine; + box.height = element.offsetWidth * sine + element.offsetHeight * cosine; + } + + var widthFactor = (parentInnerWidth - parentPadding.left - parentPadding.right) / box.width; + var heightFactor = (parentInnerHeight - parentPadding.top - parentPadding.bottom) / box.height; + var lineHeight; + + if (options.limitingDimension.toLowerCase() === "width") { + lineHeight = Math.floor(widthFactor * 1000); + parentStyle.height = lineHeight + "px"; + } else if (options.limitingDimension.toLowerCase() === "height") { + lineHeight = Math.floor(heightFactor * 1000); + } else if (widthFactor < heightFactor) + lineHeight = Math.floor(widthFactor * 1000); + else if (widthFactor >= heightFactor) + lineHeight = Math.floor(heightFactor * 1000); + + var fontSize = lineHeight * options.fontSizeFactor; + if (options.maximumFontSize !== null && fontSize > options.maximumFontSize) { + fontSize = options.maximumFontSize; + lineHeight = fontSize / options.fontSizeFactor; + } + + style.fontSize = Math.floor(fontSize) + "px"; + style.lineHeight = Math.ceil(lineHeight) + "px"; + style.marginBottom = "0px"; + style.marginRight = "0px"; + + if (options.limitingDimension.toLowerCase() === "height") { + //this option needs the font-size to be set already so computedStyle.getPropertyValue("width") returns the right size + //this +4 is to compensate the rounding erros that can occur due to the calls to Math.floor in the centering code + parentStyle.width = (parseInt(computedStyle.getPropertyValue("width")) + 4) + "px"; + } + + //Calculate the inner width and height + var innerDimensions = _calculateInnerDimensions(computedStyle); + var innerWidth = innerDimensions["width"]; + var innerHeight = innerDimensions["height"]; + + switch (options.verticalAlign.toLowerCase()) { + case "top": + style.top = "0%"; + break; + case "bottom": + style.top = "100%"; + style.marginTop = Math.floor(-innerHeight) + "px"; + break; + default: + style.marginTop = Math.ceil((-innerHeight / 2)) + "px"; + break; + } + + switch (options.horizontalAlign.toLowerCase()) { + case "left": + style.left = "0%"; + break; + case "right": + style.left = "100%"; + style.marginLeft = Math.floor(-innerWidth) + "px"; + break; + default: + style.marginLeft = Math.ceil((-innerWidth / 2)) + "px"; + break; + } + + //shows the element after the work is done + style.visibility = "visible"; + + return element; +} diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss index 1290193d0..6a0d651d2 100644 --- a/src/client/views/nodes/LabelBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -9,10 +9,10 @@ .labelBox-mainButton { max-width: 100%; - width: fit-content; - height: max-content; + width: 100%; + height: 100%; border-radius: inherit; - letter-spacing: 2px; + //letter-spacing: 2px; // bcz: doesn't work with LabelBigText text-transform: uppercase; overflow: hidden; display: inline-block; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index db1ae0537..781553dd8 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,19 +1,20 @@ -import { action, computed, observable, runInAction } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { documentSchema } from '../../../fields/documentSchemas'; import { List } from '../../../fields/List'; import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, StrCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxBaseComponent } from '../DocComponent'; +import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; +import BigText from './LabelBigText'; import './LabelBox.scss'; -import { StyleProp } from '../StyleProvider'; const LabelSchema = createSchema({}); @@ -94,13 +95,24 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit", letterSpacing: StrCast(this.layoutDoc.letterSpacing), textTransform: StrCast(this.layoutDoc.textTransform) as any, - paddingLeft: NumCast(this.layoutDoc._xPadding), - paddingRight: NumCast(this.layoutDoc._xPadding), - paddingTop: NumCast(this.layoutDoc._yPadding), - paddingBottom: NumCast(this.layoutDoc._yPadding), + width: this.props.PanelWidth(), + height: this.props.PanelHeight(), whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap" }} > - {label.startsWith("#") ? (null) : label} + { + if (r) { + BigText(r, { + rotateText: null, + fontSizeFactor: 1, + maximumFontSize: null, + limitingDimension: "both", + horizontalAlign: "center", + verticalAlign: "center", + textAlign: "center", + whiteSpace: "nowrap" + }) + } + }}>{label.startsWith("#") ? (null) : label}
    {!missingParams?.length ? (null) : missingParams.map(m =>
    {m}
    )} -- cgit v1.2.3-70-g09d2 From 904f9a88e78f4ce4e19c9c9a1c5f174cf3dbfac7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 24 Sep 2021 09:51:24 -0400 Subject: updated titling of LabelBox to show custom titles when they are set. --- src/client/views/nodes/LabelBox.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 781553dd8..c32aaeb2e 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -38,7 +38,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro } getTitle() { - return this.props.label || ""; + return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) : + this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); } protected createDropTarget = (ele: HTMLDivElement) => { @@ -82,7 +83,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ... - const label = this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); + const label = this.getTitle(); return (
    this._mouseOver = false)} @@ -91,7 +92,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}>
    Date: Fri, 24 Sep 2021 12:16:57 -0400 Subject: label box minor tweak. --- src/client/views/nodes/LabelBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index c32aaeb2e..3e75af2a1 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -38,8 +38,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro } getTitle() { - return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) : - this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); + return this.props.label ? this.props.label : this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) : + typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); } protected createDropTarget = (ele: HTMLDivElement) => { -- cgit v1.2.3-70-g09d2 From e7e67ff7e3170b6eb5bf05ffe284ca08f4802459 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 25 Sep 2021 12:30:35 -0400 Subject: added a hacky fix to what seems to be a Chrome bug when auto expanding the left flyout panel --- src/client/views/MainView.tsx | 6 ++++++ src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 35c5801e5..7edcd6217 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -467,7 +467,13 @@ export class MainView extends React.Component { } expandFlyout = action((button: Doc) => { + // bcz: What's going on here!? + // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div + // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be. + // interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);) this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250); + setTimeout(action(() => this._leftMenuFlyoutWidth += 0.5), 0); + this._sidebarContent.proto = button.target as any; this.LastButton = button; }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 81f6307d1..24a7d77e0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -646,8 +646,7 @@ export class MarqueeView extends React.Component e.preventDefault()} -- cgit v1.2.3-70-g09d2 From eed7f32cda030d9ef2d8aaae168c11832460a653 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 25 Sep 2021 16:09:21 -0400 Subject: fix for some youtube urls that didn't upload. --- src/client/views/collections/CollectionSubView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index cb8b55cb2..e662f3ddf 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -356,7 +356,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: const batch = UndoManager.StartBatch("youtube upload"); const generatedDocuments: Doc[] = []; - this.slowLoadDocuments((uriList || text).split("v=")[1], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); + this.slowLoadDocuments((uriList || text).split("v=")[1].split("&")[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); return; } -- cgit v1.2.3-70-g09d2 From 78f1befc4551d963ed2a95d30d09db515917a8bb Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 25 Sep 2021 22:01:38 -0400 Subject: from last --- src/client/views/nodes/DocumentView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 246d9f68d..6a7cd108a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -90,6 +90,7 @@ export interface DocComponentView { getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) playFrom?: (time: number, endTime?: number) => void; + Pause?: () => void; setFocus?: () => void; componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element; fieldKey?: string; @@ -853,7 +854,7 @@ export class DocumentViewInternal extends DocComponent {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} - {this.hideLinkButton ? (null) : + {this.hideLinkButton || this.props.renderDepth === -1 ? (null) :
    } -- cgit v1.2.3-70-g09d2 From 27baf4ef57b2816b3c5147f38eddc9fac2ed6907 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 25 Sep 2021 22:04:14 -0400 Subject: fixed warning --- src/client/views/nodes/LabelBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 3e75af2a1..90b9ce55d 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -111,7 +111,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro verticalAlign: "center", textAlign: "center", whiteSpace: "nowrap" - }) + }); } }}>{label.startsWith("#") ? (null) : label}
    -- cgit v1.2.3-70-g09d2 From 67703bcbc6dbc01e360d07de19648a9faab4899b Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 26 Sep 2021 23:34:32 -0400 Subject: don't delete tab if it's selected and backspace is typed. --- src/client/views/GlobalKeyHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index de6f4ae8b..574d28b8f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -141,7 +141,7 @@ export class KeyManager { case "delete": case "backspace": if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") { - const selected = SelectionManager.Views().slice(); + const selected = SelectionManager.Views().filter(dv => !dv.topMost);; UndoManager.RunInBatch(() => { SelectionManager.DeselectAll(); selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document)); -- cgit v1.2.3-70-g09d2 From f70aa7ba42153b2c283b7b9af04a5e75ea19a7ad Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Sep 2021 09:49:26 -0400 Subject: made docs with no author Admin for everyone. fixed run-time warnings: not memoized accesses when panning, NaNs drawing some labelBoxes, filter box filter boolean options, --- .../collectionFreeForm/CollectionFreeFormView.tsx | 20 +++++++++++++------- src/client/views/nodes/DocumentView.tsx | 6 +++--- src/client/views/nodes/FilterBox.tsx | 6 +++--- src/fields/util.ts | 3 ++- 4 files changed, 21 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5b9f6139f..b2db1168d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { DateField } from "../../../../fields/DateField"; @@ -811,14 +811,14 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout instanceof Doc).map(pair => pair.layout); - const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "") && this.childSizeProvider(doc, "")). - map(doc => ({ ...this.childDataProvider(doc, ""), ...this.childSizeProvider(doc, "") })); + const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc); + const measuredDocs = docs.map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ""), size: this.childSizeProviderUnmemoized(doc, "") })) + .filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! })); if (measuredDocs.length) { - const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content + const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) => // computes range of content ({ - xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) }, - yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) } + xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) }, + yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) } }) , { xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }, @@ -1105,10 +1105,16 @@ export class CollectionFreeFormView extends CollectionSubView { + return this._layoutPoolData.get(doc[Id] + (replica || "")); + } childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) { return this._layoutPoolData.get(doc[Id] + (replica || "")); }.bind(this)); + childSizeProviderUnmemoized = (doc: Doc, replica: string) => { + return this._layoutSizeData.get(doc[Id] + (replica || "")); + } childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) { return this._layoutSizeData.get(doc[Id] + (replica || "")); }.bind(this)); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6a7cd108a..1187521b3 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1147,10 +1147,10 @@ export class DocumentView extends React.Component { @computed get nativeScaling() { if (this.shouldNotScale) return 1; const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0; - if (this.fitWidth || this.props.PanelHeight() / this.effectiveNativeHeight > this.props.PanelWidth() / this.effectiveNativeWidth) { - return Math.max(minTextScale, this.props.PanelWidth() / this.effectiveNativeWidth); // width-limited or fitWidth + if (this.fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) { + return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or fitWidth } - return Math.max(minTextScale, this.props.PanelHeight() / this.effectiveNativeHeight); // height-limited or unscaled + return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled } @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); } diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index e9f19bf9e..2041c7399 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -377,9 +377,9 @@ export class FilterBox extends ViewBoxBaseComponent
    - + +
    filters together
    diff --git a/src/fields/util.ts b/src/fields/util.ts index 439c4d333..3590c2dea 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -191,7 +191,8 @@ let HierarchyMapping: Map | undefined; function getEffectiveAcl(target: any, user?: string): symbol { const targetAcls = target[AclSym]; const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group - if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; // target may be a Doc of Proxy, so check __fields.author and .author + const targetAuthor = (target.__fields?.author || target.author); // target may be a Doc of Proxy, so check __fields.author and .author + if (userChecked === targetAuthor || !targetAuthor) return AclAdmin; if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin; if (targetAcls && Object.keys(targetAcls).length) { -- cgit v1.2.3-70-g09d2 From 9d062a00812ee629c9af3e776db98cc184e746f9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Sep 2021 10:17:38 -0400 Subject: improved image resizing flickering caused by Chrome bug with overflow:auto. works now when fitWidth is false. --- src/client/views/nodes/ImageBox.scss | 1 - src/client/views/nodes/ImageBox.tsx | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 2536dbe16..9142a68ed 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -101,7 +101,6 @@ margin: 0 auto; display: flex; height: 100%; - overflow: auto; .imageBox-fadeBlocker { width: 100%; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index b41bfd3ea..b927e98db 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -81,11 +81,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent this._curSuffix = forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o", { fireImmediately: true, delay: 1000 }); - this._disposers.selection = reaction(() => this.props.isSelected(), - selected => !selected && setTimeout(() => { - // Array.from(this._savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); - // this._savedAnnotations.clear(); - })); this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { if (!this.layoutDoc._height) { @@ -291,7 +286,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent -
    +
    Date: Mon, 27 Sep 2021 14:06:51 -0400 Subject: videoBox fitsWidth properly in lightbox. documentdecorations less hacky for deciding when to allow nativewidth/height changes by adding props to document type templates. fixed height of properties pane & updated CSS for leftflyout & properties pane. marquees no longer oveflow image/video boxes. --- src/client/documents/Documents.ts | 12 +++++++++--- src/client/views/DocumentDecorations.tsx | 29 +++++++++++------------------ src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/MainView.scss | 9 ++++----- src/client/views/MainView.tsx | 12 ++++++------ src/client/views/nodes/ImageBox.scss | 1 + src/client/views/nodes/ImageBox.tsx | 4 ++-- src/client/views/nodes/VideoBox.scss | 15 ++++++++++++++- src/client/views/nodes/VideoBox.tsx | 22 +++++++++++++++------- src/client/views/nodes/WebBox.tsx | 8 ++++---- 10 files changed, 67 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8ac647b99..403620bdd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -352,7 +352,7 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.RTF, { layout: { view: FormattedTextBox, dataField: "text" }, - options: { _height: 150, _xMargin: 10, _yMargin: 10, links: ComputedField.MakeFunction("links(self)") as any } + options: { _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.SEARCH, { layout: { view: SearchBox, dataField: defaultDataKey }, @@ -372,7 +372,7 @@ export namespace Docs { }], [DocumentType.WEB, { layout: { view: WebBox, dataField: defaultDataKey }, - options: { _height: 300, _fitWidth: true, links: ComputedField.MakeFunction("links(self)") as any } + options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.COL, { layout: { view: CollectionView, dataField: defaultDataKey }, @@ -392,7 +392,7 @@ export namespace Docs { }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: defaultDataKey }, - options: { _curPage: 1, _fitWidth: true, links: ComputedField.MakeFunction("links(self)") as any } + options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } }], [DocumentType.IMPORT, { layout: { view: DirectoryImportBox, dataField: defaultDataKey }, @@ -506,6 +506,12 @@ export namespace Docs { const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix); // fetch the actual prototype documents from the server const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds); + Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeHeightUnfrozen = true; // to avoid having to recreate the DB + Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeHeightUnfrozen = true; + Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeHeightUnfrozen = true; + Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeDimModifiable = true; // to avoid having to recreate the DB + Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeDimModifiable = true; + Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeDimModifiable = true; // update this object to include any default values: DocumentOptions for all prototypes prototypeIds.map(id => { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index e9a54d6a5..bd61c9f60 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -319,12 +319,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P let height = (doc._height || (nheight / nwidth * width)); height = !height || isNaN(height) ? 20 : height; const scale = docView.props.ScreenToLocalTransform().Scale; - const canModifyNativeDim = e.ctrlKey || doc.allowReflow; + const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable; if (nwidth && nheight) { if (nwidth / nheight !== width / height && !dragBottom) { height = nheight / nwidth * width; } - if (canModifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction + if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; else dW = dH * nwidth / nheight; } @@ -334,34 +334,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P doc.x = (doc.x || 0) + dX * (actualdW - width); doc.y = (doc.y || 0) + dY * (actualdH - height); const fixedAspect = (nwidth && nheight); - if (canModifyNativeDim && [DocumentType.IMG, DocumentType.SCREENSHOT, DocumentType.VID].includes(doc.type as DocumentType)) { - dW !== 0 && runInAction(() => { - const dataDoc = doc[DataSym]; - const nw = Doc.NativeWidth(dataDoc); - const nh = Doc.NativeHeight(dataDoc); - Doc.SetNativeWidth(dataDoc, nw + (dW > 0 ? 10 : -10)); - Doc.SetNativeHeight(dataDoc, nh + (dW > 0 ? 10 : -10) * nh / nw); - }); - } - else if (fixedAspect) { - if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !canModifyNativeDim)) || dragRight) { - if (dragRight && canModifyNativeDim) { + if (fixedAspect) { + if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) { + if (dragRight && modifyNativeDim) { doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc); } else { if (!doc._fitWidth) doc._height = nheight / nwidth * actualdW; - else if (!canModifyNativeDim || dragBotRight) doc._height = actualdH; + else if (!modifyNativeDim || dragBotRight) doc._height = actualdH; } doc._width = actualdW; } else { - if (dragBottom && (canModifyNativeDim || docView.layoutDoc._fitWidth)) { // frozen web pages and others that fitWidth can't grow horizontally to match a vertical resize so the only choice is to change the nativeheight even if the ctrl key isn't used + if (dragBottom && (modifyNativeDim || + (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used) doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc); doc._autoHeight = false; } else { if (!doc._fitWidth) doc._width = nwidth / nheight * actualdH; - else if (!canModifyNativeDim || dragBotRight) doc._width = actualdW; + else if (!modifyNativeDim || dragBotRight) doc._width = actualdW; } - doc._height = actualdH; + if (!modifyNativeDim) doc._height = Math.min(nheight / nwidth * NumCast(doc._width), actualdH); + else doc._height = actualdH; } } else { dH && (doc._height = actualdH); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 574d28b8f..364bf05e2 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -141,7 +141,7 @@ export class KeyManager { case "delete": case "backspace": if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") { - const selected = SelectionManager.Views().filter(dv => !dv.topMost);; + const selected = SelectionManager.Views().filter(dv => !dv.topMost); UndoManager.RunInBatch(() => { SelectionManager.DeselectAll(); selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document)); diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 7fa841002..c3cdb7dde 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -109,9 +109,9 @@ .properties-container { height: 100%; - position: relative; - left: 100%; - top: calc(-100% - 36px); + position: absolute; + right: 0; + top: 0; z-index: 3000; } @@ -298,9 +298,8 @@ width: var(--flyoutHandleWidth); height: 55px; top: 50%; - left: -10px; border-radius: 8px; - position: relative; + position: absolute; z-index: 41; // lm_maximised has a z-index of 40 and this needs to be above that touch-action: none; cursor: grab; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7edcd6217..3f7df705f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -426,23 +426,23 @@ export class MainView extends React.Component { } @computed get mainInnerContent() { - const width = this.propertiesWidth() + this._leftMenuFlyoutWidth + this.leftMenuWidth(); - const transform = this._leftMenuFlyoutWidth ? 'translate(-28px, 0px)' : undefined; + const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth(); + const width = this.propertiesWidth() + leftMenuFlyoutWidth; return <> {this.leftMenuPanel}
    {this.flyout} -
    +
    -
    +
    {this.dockingContent} -
    +
    -
    +
    {this.propertiesWidth() < 10 ? (null) : }
    diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 9142a68ed..4238f6d29 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -13,6 +13,7 @@ width: 100%; pointer-events: none; mix-blend-mode: multiply; // bcz: makes text fuzzy! + overflow: hidden; } .imageBox-fader { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index b927e98db..89f70985c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -354,15 +354,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent ; + return
    ; } marqueeDown = (e: React.PointerEvent) => { @@ -568,10 +568,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent [this.youtubeVideoId ? this.youtubeContent : this.content]; scaling = () => this.props.scaling?.() || 1; - panelWidth = () => this.props.PanelWidth() * this.heightPercent / 100; - panelHeight = () => this.layoutDoc._fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : this.props.PanelHeight() * this.heightPercent / 100; + panelWidth = (): number => this.fitWidth ? this.props.PanelWidth() : (Doc.NativeAspect(this.rootDoc) || 1) * this.panelHeight(); + panelHeight = (): number => this.fitWidth ? this.panelWidth() / (Doc.NativeAspect(this.rootDoc) || 1) : this.heightPercent / 100 * this.props.PanelHeight(); screenToLocalTransform = () => { const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling(); return this.props.ScreenToLocalTransform().translate(-offset, 0).scale(100 / this.heightPercent); @@ -585,10 +586,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent { e.stopPropagation(); e.preventDefault(); }}>
    -
    +
    - {this.uIButtons} {this.annotationLayer} - {this.renderTimeline} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : } + {this.renderTimeline} + {this.uIButtons}
    ); } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 8e6586735..37d716993 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -430,9 +430,9 @@ export class WebBox extends ViewBoxAnnotatableComponent this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); funcs.push({ - description: (!this.layoutDoc.allowReflow ? "Allow" : "Prevent") + " Reflow", event: () => { - const nw = !this.layoutDoc.allowReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); - this.layoutDoc.allowReflow = !nw; + description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => { + const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); + this.layoutDoc.forceReflow = !nw; if (nw) { Doc.SetInPlace(this.layoutDoc, this.fieldKey + "-nativeWidth", nw, true); } @@ -536,7 +536,7 @@ export class WebBox extends ViewBoxAnnotatableComponent + style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}> {this.urlContent}
    ; } -- cgit v1.2.3-70-g09d2 From eb529611c97c9936577697b829c50b4ca0736c6e Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Sep 2021 14:32:40 -0400 Subject: fixed sharing panel css to avoid overlaps --- src/client/util/SharingManager.scss | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index 9dc57dd1e..2de636f21 100644 --- a/src/client/util/SharingManager.scss +++ b/src/client/util/SharingManager.scss @@ -40,7 +40,6 @@ .permissions-select { z-index: 1; - margin-left: -115; border: none; outline: none; text-align: justify; // for Edge -- cgit v1.2.3-70-g09d2 From 1561e37eb966607564938530a71aeb7e3ba27583 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Sep 2021 23:03:47 -0400 Subject: fixed ink stroke editing to work with closed curves (eg break / restore tangent & drag start/end point). changed ink rendering to use svg bezier curves. fixed bugs with keydown handler to break tangent. --- src/client/util/InteractionUtils.tsx | 51 +++++++++------------------------ src/client/views/GestureOverlay.tsx | 4 +-- src/client/views/InkControls.tsx | 10 +++++-- src/client/views/InkHandles.tsx | 38 +++++++++++++----------- src/client/views/InkStrokeProperties.ts | 11 ++++--- src/client/views/InkingStroke.tsx | 4 +-- 6 files changed, 53 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 4a8011e3c..66afc849e 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -146,7 +146,7 @@ export namespace InteractionUtils { if (shape) { //if any of the shape are true pts = makePolygon(shape, points); } - else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) { + else if (((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) && !bezier) { for (var i = 0; i < points.length - 3; i += 4) { const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]]; for (var t = 0; t < 1; t += 0.01) { @@ -154,47 +154,22 @@ export namespace InteractionUtils { pts.push({ X: point[0], Y: point[1] }); } } - } - else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) { - //pointer is up (first and last points are the same) - const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); - newPoints.pop(); - - const bezierCurves = fitCurve(newPoints, parseInt(bezier)); - for (const curve of bezierCurves) { - for (var t = 0; t < 1; t += 0.01) { - const point = beziercurve(t, curve); - pts.push({ X: point[0], Y: point[1] }); - } - } } else { pts = points.slice(); - // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag. need to remove it here. - if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) { - pts.pop(); - } - } - if (isNaN(scalex)) { - scalex = 1; - } - if (isNaN(scaley)) { - scaley = 1; } - const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc + - `${(pt.X - left - width / 2) * scalex + width / 2}, - ${(pt.Y - top - width / 2) * scaley + width / 2} `, ""); + if (isNaN(scalex)) scalex = 1; + if (isNaN(scaley)) scaley = 1; + + const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `; + var strpts = bezier ? + pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pts[i]) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : + pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, ""); + const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; const defGuid = Utils.GenerateGuid(); const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth))); - const addables = pts.map((pts, i) => - - { console.log(i); }} pointerEvents="all" cursor="all-scroll" - /> - ); - - + const Tag = (bezier ? "path" : "polyline") as keyof JSX.IntrinsicElements; return ( {/* setting the svg fill sets the arrowStart fill */} {nodefs ? (null) : {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : @@ -207,8 +182,10 @@ export namespace InteractionUtils { } } - - {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} + {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "", ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} ] ]; } diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx index 4df7ee813..7e685288d 100644 --- a/src/client/views/InkControls.tsx +++ b/src/client/views/InkControls.tsx @@ -37,8 +37,8 @@ export class InkControls extends React.Component { const controlUndo = UndoManager.StartBatch("DocDecs set radius"); const screenScale = this.props.ScreenToLocalTransform().Scale; const order = controlIndex % 4; - const handleIndexA = order === 2 ? controlIndex - 1 : controlIndex - 2; - const handleIndexB = order === 2 ? controlIndex + 2 : controlIndex + 1; + const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; + const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { @@ -47,7 +47,11 @@ export class InkControls extends React.Component { }, () => controlUndo?.end(), action((e: PointerEvent, doubleTap: boolean | undefined) => { - if (doubleTap && brokenIndices && brokenIndices.includes(controlIndex)) { + const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; + if (doubleTap && brokenIndices?.includes(equivIndex)) { + InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); + } + if (doubleTap && brokenIndices?.includes(controlIndex)) { InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); } })); diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx index afe94cdfb..1a514bdce 100644 --- a/src/client/views/InkHandles.tsx +++ b/src/client/views/InkHandles.tsx @@ -31,10 +31,11 @@ export class InkHandles extends React.Component { const controlUndo = UndoManager.StartBatch("DocDecs set radius"); const screenScale = this.props.ScreenToLocalTransform().Scale; const order = handleIndex % 4; - const oppositeHandleIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; - const controlIndex = order === 1 ? handleIndex - 1 : handleIndex + 2; - document.addEventListener("keydown", (e: KeyboardEvent) => this.onBreakTangent(e, controlIndex), true); + const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; + const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.data.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.data.length; + const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.data.length; setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { + if (e.altKey) this.onBreakTangent(controlIndex); InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); return false; }, () => controlUndo?.end(), emptyFunction @@ -48,15 +49,14 @@ export class InkHandles extends React.Component { * @param handleNum The index of the currently selected handle point. */ @action - onBreakTangent = (e: KeyboardEvent, controlIndex: number) => { - const doc: Doc = this.props.inkDoc; - if (["Alt"].includes(e.key)) { - e.stopPropagation(); - if (doc) { - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; - if (brokenIndices && !brokenIndices.includes(controlIndex)) { - brokenIndices.push(controlIndex); - } + onBreakTangent = (controlIndex: number) => { + const doc = this.props.inkDoc; + if (doc) { + const closed = this.props.data.lastElement().X === this.props.data[0].X && this.props.data.lastElement().Y === this.props.data[0].Y; + const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; + if (!brokenIndices?.includes(controlIndex) && + ((controlIndex > 0 && controlIndex < this.props.data.length - 1) || closed)) { + brokenIndices.push(controlIndex); doc.brokenInkIndices = brokenIndices; } } @@ -70,14 +70,20 @@ export class InkHandles extends React.Component { const data = this.props.data; const handlePoints: HandlePoint[] = []; const handleLines: HandleLine[] = []; + const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; if (data.length >= 4) { for (let i = 0; i <= data.length - 4; i += 4) { - handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 }); - handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 }); + handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); + handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); } // Adding first and last (single) handle lines. - handleLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); - handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); + if (closed) { + handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); + } + else { + handleLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); + handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); + } for (let i = 2; i < data.length - 4; i += 4) { handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 42190238e..2073497b9 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,14 +1,14 @@ import { action, computed, observable, reaction } from "mobx"; -import { Doc, DocListCast, Field, Opt } from "../../fields/Doc"; +import { Doc, Field, Opt } from "../../fields/Doc"; import { Document } from "../../fields/documentSchemas"; -import { InkField, InkData, PointData, ControlPoint, InkTool } from "../../fields/InkField"; +import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast, NumCast } from "../../fields/Types"; import { DocumentType } from "../documents/DocumentTypes"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; @@ -241,6 +241,7 @@ export class InkStrokeProperties { this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const newPoints: { X: number, Y: number }[] = []; const order = controlIndex % 4; + const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; for (var i = 0; i < ink.length; i++) { const leftHandlePoint = order === 0 && i === controlIndex + 1; const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; @@ -248,6 +249,7 @@ export class InkStrokeProperties { leftHandlePoint || rightHandlePoint || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || + ((order === 0 || order === 3) && (controlIndex === 0 || controlIndex === ink.length - 1) && (i === 1 || i === ink.length - 2) && closed) || (order === 3 && i === controlIndex - 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || @@ -335,6 +337,7 @@ export class InkStrokeProperties { @action moveHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { + const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; const oldHandlePoint = ink[handleIndex]; let oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; @@ -342,7 +345,7 @@ export class InkStrokeProperties { ink[handleIndex] = newHandlePoint; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). - if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && handleIndex !== 1 && handleIndex !== ink.length - 2) { + if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); ink[oppositeHandleIndex] = oppositeHandlePoint; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index ca39bdaa1..a518bf07b 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -11,14 +11,11 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { InteractionUtils } from "../util/InteractionUtils"; -import { Scripting } from "../util/Scripting"; import { ContextMenu } from "./ContextMenu"; import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; import { InkControls } from "./InkControls"; import { InkHandles } from "./InkHandles"; -import { GestureOverlay } from "./GestureOverlay"; -import { isThisTypeNode } from "typescript"; import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; @@ -83,6 +80,7 @@ export class InkingStroke extends ViewBoxBaseComponent Date: Tue, 28 Sep 2021 13:59:28 -0400 Subject: converted ink addpoints to not use sampling. needs addPoints() to be filled in. --- package-lock.json | 10 +++ package.json | 2 + src/client/util/InteractionUtils.tsx | 65 +----------------- src/client/views/InkControls.tsx | 46 ++++++------- src/client/views/InkStrokeProperties.ts | 116 +++++++++++++++++--------------- src/client/views/InkingStroke.tsx | 50 +++++++++++--- 6 files changed, 138 insertions(+), 151 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index 7810e3120..c1dd8506f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -616,6 +616,11 @@ "integrity": "sha1-TN2WtJKTs5MhIuS34pVD415rrlg=", "dev": true }, + "@types/bezier-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/bezier-js/-/bezier-js-4.1.0.tgz", + "integrity": "sha512-ElU16s8E6Pr6magp8ihwH1O8pbUJASbMND/qgUc9RsLmP3lMLHiDMRXdjtaObwW5GPtOVYOsXDUIhTIluT+yaw==" + }, "@types/bluebird": { "version": "3.5.32", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.32.tgz", @@ -2628,6 +2633,11 @@ "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz", "integrity": "sha1-o9+v6rEqlMRicw1QeYxSqEBdc3k=" }, + "bezier-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-4.1.1.tgz", + "integrity": "sha512-oVOS6SSFFFlfnZdzC+lsfvhs/RRcbxJ47U04M4s5QIBaJmr3YWmTIL3qmrOK9uW+nUUcl9Jccmo/xpTrG+bBoQ==" + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", diff --git a/package.json b/package.json index 6cbde9e1f..5d10c0d54 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "@hig/theme-data": "^2.16.1", "@material-ui/core": "^4.11.0", "@react-three/fiber": "^6.0.16", + "@types/bezier-js": "^4.1.0", "@types/cors": "^2.8.8", "@types/d3-axis": "^2.0.0", "@types/d3-color": "^2.0.1", @@ -145,6 +146,7 @@ "babel-runtime": "^6.26.0", "bcrypt-nodejs": "0.0.3", "bezier-curve": "^1.0.0", + "bezier-js": "^4.1.1", "bluebird": "^3.7.2", "body-parser": "^1.18.3", "bootstrap": "^4.5.0", diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 66afc849e..f748188d7 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,10 +1,8 @@ import React = require("react"); import * as beziercurve from 'bezier-curve'; import * as fitCurve from 'fit-curve'; -import "./InteractionUtils.scss"; import { Utils } from "../../Utils"; -import { CurrentUserUtils } from "./CurrentUserUtils"; -import { InkTool } from "../../fields/InkField"; +import "./InteractionUtils.scss"; export namespace InteractionUtils { export const MOUSETYPE = "mouse"; @@ -93,70 +91,13 @@ export namespace InteractionUtils { return myTouches; } - export function CreatePoints(points: { X: number, Y: number }[], left: number, top: number, - color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, - dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) { - let pts: { X: number; Y: number; }[] = []; - if (shape) { //if any of the shape are true - pts = makePolygon(shape, points); - } - else if ((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) { - for (var i = 0; i < points.length - 3; i += 4) { - const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]]; - for (var t = 0; t < 1; t += 0.01) { - const point = beziercurve(t, array); - pts.push({ X: point[0], Y: point[1] }); - } - } - } - else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) { - //pointer is up (first and last points are the same) - const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); - newPoints.pop(); - - const bezierCurves = fitCurve(newPoints, parseInt(bezier)); - for (const curve of bezierCurves) { - for (var t = 0; t < 1; t += 0.01) { - const point = beziercurve(t, curve); - pts.push({ X: point[0], Y: point[1] }); - } - } - } else { - pts = points.slice(); - // bcz: Ugh... this is ugly, but shapes apprently have an extra point added that is = (p[0].x,p[0].y+1) as some sort of flag. need to remove it here. - if (pts.length > 2 && pts[pts.length - 2].X === pts[0].X && pts[pts.length - 2].Y === pts[0].Y) { - pts.pop(); - } - } - if (isNaN(scalex)) { - scalex = 1; - } - if (isNaN(scaley)) { - scaley = 1; - } - return pts; - } - - - export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) { let pts: { X: number; Y: number; }[] = []; if (shape) { //if any of the shape are true - pts = makePolygon(shape, points); - } - else if (((points.length >= 5 && points[3].X === points[4].X) || (points.length === 4)) && !bezier) { - for (var i = 0; i < points.length - 3; i += 4) { - const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]]; - for (var t = 0; t < 1; t += 0.01) { - const point = beziercurve(t, array); - pts.push({ X: point[0], Y: point[1] }); - } - } - } else { - pts = points.slice(); - } + const pts = shape ? makePolygon(shape, points) : points; + if (isNaN(scalex)) scalex = 1; if (isNaN(scaley)) scaley = 1; diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx index 7e685288d..5fe0c0f8a 100644 --- a/src/client/views/InkControls.tsx +++ b/src/client/views/InkControls.tsx @@ -5,7 +5,7 @@ import { Doc } from "../../fields/Doc"; import { ControlPoint, InkData, PointData } from "../../fields/InkField"; import { listSpec } from "../../fields/Schema"; import { Cast } from "../../fields/Types"; -import { setupMoveUpEvents } from "../../Utils"; +import { setupMoveUpEvents, Utils } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; @@ -15,17 +15,16 @@ export interface InkControlProps { inkDoc: Doc; inkCtrlPoints: InkData; screenCtrlPoints: InkData; - inkStrokeSamplePts: PointData[]; - screenStrokeSamplePoints: PointData[]; format: number[]; ScreenToLocalTransform: () => Transform; + nearestScreenPt: () => PointData | undefined; } @observer export class InkControls extends React.Component { + @observable private _overControl = -1; @observable private _overAddPoint = -1; - /** * Handles the movement of a selected control point when the user clicks and drags. * @param controlIndex The index of the currently selected control point. @@ -57,6 +56,14 @@ export class InkControls extends React.Component { })); } } + /** + * Updates whether a user has hovered over a particular control point or point that could be added + * on click. + */ + @action onEnterControl = (i: number) => { this._overControl = i; }; + @action onLeaveControl = () => { this._overControl = -1; }; + @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; + @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; /** * Deletes the currently selected point. @@ -79,19 +86,11 @@ export class InkControls extends React.Component { } } - /** - * Updates whether a user has hovered over a particular control point or point that could be added - * on click. - */ - @action onEnterControl = (i: number) => { this._overControl = i; }; - @action onLeaveControl = () => { this._overControl = -1; }; - @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; - @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; - render() { const formatInstance = InkStrokeProperties.Instance; if (!formatInstance) return (null); + // Accessing the current ink's data and extracting all control points. const scrData = this.props.screenCtrlPoints; const sreenCtrlPoints: ControlPoint[] = []; @@ -109,24 +108,23 @@ export class InkControls extends React.Component { const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format; const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; + + const nearestScreenPt = this.props.nearestScreenPt(); return ( {/* should really have just one circle here that represents the neqraest point on the stroke to the users hover point. This points should be passed as a prop from InkingStroke's UI which should set it in its onPointerOver method */} - {this.props.screenStrokeSamplePoints.map((pts, i) => - formatInstance?.addPoints(this.props.inkStrokeSamplePts[i].X, this.props.inkStrokeSamplePts[i].Y, this.props.inkStrokeSamplePts, i, inkCtrlPts)} - onMouseEnter={() => this.onEnterAddPoint(i)} - onMouseLeave={this.onLeaveAddPoint} - pointerEvents="all" + pointerEvents="none" cursor="all-scroll" /> - )} + } {sreenCtrlPoints.map((control, i) => { - this.applyFunction((doc: Doc, ink: InkData) => { - const newControl = { X: x, Y: y }; - const newPoints: InkData = []; - let [counter, start, end] = [0, 0, 0]; - for (let k = 0; k < points.length; k++) { - if (end === 0) { - controls.forEach((control) => { - if (control.X === points[k].X && control.Y === points[k].Y) { - if (k < index) { - counter++; - start = k; - } else if (k > index) { - end = k; - } - } - }); - } - } - if (end === 0) end = points.length - 1; - // Index of new control point with regards to the ink data. - const newIndex = Math.floor(counter / 2) * 4 + 2; - // Creating new ink data with the new control point and handle points inputted. - for (let i = 0; i < ink.length; i++) { - if (i === newIndex) { - const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index + 1), points.slice(index, end), newControl); - newPoints.push(handleA, newControl, newControl, handleB); - // Adjusting the magnitude of the left handle line of the right neighboring control point. - const [rightControl, rightHandle] = [points[end], ink[i]]; - const scaledVector = this.getScaledHandlePoint(false, start, end, index, rightControl, rightHandle); - rightHandle && newPoints.push({ X: rightControl.X - scaledVector.X, Y: rightControl.Y - scaledVector.Y }); - } else if (i === newIndex - 1) { - // Adjusting the magnitude of the right handle line of the left neighboring control point. - const [leftControl, leftHandle] = [points[start], ink[i]]; - const scaledVector = this.getScaledHandlePoint(true, start, end, index, leftControl, leftHandle); - leftHandle && newPoints.push({ X: leftControl.X - scaledVector.X, Y: leftControl.Y - scaledVector.Y }); - } else { - ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y }); - } + addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => { + const array = [{ x: controls[i].X, y: controls[i].Y }, { x: controls[i + 1].X, y: controls[i + 1].Y }, { x: controls[i + 2].X, y: controls[i + 2].Y }, { x: controls[i + 3].X, y: controls[i + 3].Y }]; + const newsegs = new Bezier(array).split(t); + // this.applyFunction((doc: Doc, ink: InkData) => { + // const newControl = { X: x, Y: y }; + // const newPoints: InkData = []; + // let [counter, start, end] = [0, 0, 0]; + // for (let k = 0; k < points.length; k++) { + // if (end === 0) { + // controls.forEach((control) => { + // if (control.X === points[k].X && control.Y === points[k].Y) { + // if (k < index) { + // counter++; + // start = k; + // } else if (k > index) { + // end = k; + // } + // } + // }); + // } + // } + // if (end === 0) end = points.length - 1; + // // Index of new control point with regards to the ink data. + // const newIndex = Math.floor(counter / 2) * 4 + 2; + // // Creating new ink data with the new control point and handle points inputted. + // for (let i = 0; i < ink.length; i++) { + // if (i === newIndex) { + // const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index + 1), points.slice(index, end), newControl); + // newPoints.push(handleA, newControl, newControl, handleB); + // // Adjusting the magnitude of the left handle line of the right neighboring control point. + // const [rightControl, rightHandle] = [points[end], ink[i]]; + // const scaledVector = this.getScaledHandlePoint(false, start, end, index, rightControl, rightHandle); + // rightHandle && newPoints.push({ X: rightControl.X - scaledVector.X, Y: rightControl.Y - scaledVector.Y }); + // } else if (i === newIndex - 1) { + // // Adjusting the magnitude of the right handle line of the left neighboring control point. + // const [leftControl, leftHandle] = [points[start], ink[i]]; + // const scaledVector = this.getScaledHandlePoint(true, start, end, index, leftControl, leftHandle); + // leftHandle && newPoints.push({ X: leftControl.X - scaledVector.X, Y: leftControl.Y - scaledVector.Y }); + // } else { + // ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y }); + // } - } - let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); - // Updating the indices of the control points whose handle tangency has been broken. - if (brokenIndices) { - brokenIndices = new List(brokenIndices.map((control) => { - if (control >= newIndex) { - return control + 4; - } else { - return control; - } - })); - } - doc.brokenInkIndices = brokenIndices; - this._currentPoint = -1; - return newPoints; - }); + // } + // let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); + // // Updating the indices of the control points whose handle tangency has been broken. + // if (brokenIndices) { + // brokenIndices = new List(brokenIndices.map((control) => { + // if (control >= newIndex) { + // return control + 4; + // } else { + // return control; + // } + // })); + // } + // doc.brokenInkIndices = brokenIndices; + // this._currentPoint = -1; + // return newPoints; + // }); } /** diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index a518bf07b..efe2e5f66 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -16,6 +16,7 @@ import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; import { InkControls } from "./InkControls"; import { InkHandles } from "./InkHandles"; +import { Bezier } from "bezier-js"; import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; @@ -82,6 +83,8 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt; + + @action + onPointerOver = (e: React.PointerEvent) => { + const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); + const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth)[0]; + const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(point.X, point.Y)).map(p => ({ X: p[0], Y: p[1] })); + const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth / 2; + const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth / 2; + var nearest = Number.MAX_SAFE_INTEGER; + this._nearestT = -1; + this._nearestSeg = -1; + this._nearestScrPt = { X: 0, Y: 0 }; + for (var i = 0; i < screenPts.length - 3; i += 4) { + const array = [{ x: screenPts[i].X, y: screenPts[i].Y }, { x: screenPts[i + 1].X, y: screenPts[i + 1].Y }, { x: screenPts[i + 2].X, y: screenPts[i + 2].Y }, { x: screenPts[i + 3].X, y: screenPts[i + 3].Y }]; + const point = new Bezier(array).project({ x: e.clientX + screenLeft - screenOrigin[0], y: e.clientY + screenTop - screenOrigin[1] }); + if (point.t) { + const dist = (point.x - screenLeft - e.clientX + screenOrigin[0]) * (point.x - screenLeft - e.clientX + screenOrigin[0]) + + (point.y - screenTop - e.clientY + screenOrigin[1]) * (point.y - screenTop - e.clientY + screenOrigin[1]); + if (dist < nearest) { + nearest = dist; + this._nearestT = point.t; + this._nearestSeg = i; + this._nearestScrPt = { X: point.x, Y: point.y }; + } + } + } + } componentUI = (boundsLeft: number, boundsTop: number) => { const inkDoc = this.props.Document; @@ -141,13 +177,6 @@ export class InkingStroke extends ViewBoxBaseComponent p.X)) - screenInkWidth[0] / 2; const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - const screenSpaceSamplePoints = InteractionUtils.CreatePoints(screenPts, screenLeft, screenTop, StrCast(inkDoc.strokeColor, "none"), screenInkWidth[0], screenSpaceCenterlineStrokeWidth, - StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker), - StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", this.props.isSelected() && inkStrokeWidth <= 5, false); - const inkSpaceSamplePoints = InteractionUtils.CreatePoints(inkData, inkLeft, inkTop, StrCast(inkDoc.strokeColor, "none"), inkStrokeWidth, screenSpaceCenterlineStrokeWidth, - StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker), - StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), 1, 1, "", "none", this.props.isSelected() && inkStrokeWidth <= 5, false); - return
    + this._nearestScrPt = undefined + )} + onPointerMove={this.onPointerOver} onPointerDown={this.onPointerDown} onClick={this.onClick} onContextMenu={() => { -- cgit v1.2.3-70-g09d2 From 2a6385ad330ac4d7caf6b23e64cb13b7546ea44a Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 13:59:37 -0400 Subject: from last --- src/client/util/InteractionUtils.tsx | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index f748188d7..9f3159d83 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -94,8 +94,6 @@ export namespace InteractionUtils { export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) { - let pts: { X: number; Y: number; }[] = []; - if (shape) { //if any of the shape are true const pts = shape ? makePolygon(shape, points) : points; if (isNaN(scalex)) scalex = 1; -- cgit v1.2.3-70-g09d2 From 4dd8a5a4110106a607156d20b5315f28e8a2c163 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 14:31:19 -0400 Subject: only hover ink if its selected --- src/client/views/InkingStroke.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index efe2e5f66..60dc7b9f7 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -83,10 +83,11 @@ export class InkingStroke extends ViewBoxBaseComponent { + this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this._nearestT, this._nearestSeg, this.inkScaledData().inkData); + } ); } } @@ -236,7 +237,7 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt = undefined )} - onPointerMove={this.onPointerOver} + onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined} onPointerDown={this.onPointerDown} onClick={this.onClick} onContextMenu={() => { -- cgit v1.2.3-70-g09d2 From 84ebdbe652fb38fb16a02c294739b5a26365d12b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 14:38:35 -0400 Subject: fixed double click to edit curve. --- src/client/views/InkingStroke.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 60dc7b9f7..83fe04893 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -83,11 +83,10 @@ export class InkingStroke extends ViewBoxBaseComponent { - this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this._nearestT, this._nearestSeg, this.inkScaledData().inkData); - } + }), this._properties?._controlButton, this._properties?._controlButton ); } } -- cgit v1.2.3-70-g09d2 From e4a0d2c5752f541926d1fa6b36a284b9b3d69af9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 15:06:25 -0400 Subject: added the splice --- src/client/views/InkStrokeProperties.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 03946bb60..6b4cf9e50 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -78,8 +78,12 @@ export class InkStrokeProperties { @undoBatch @action addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => { - const array = [{ x: controls[i].X, y: controls[i].Y }, { x: controls[i + 1].X, y: controls[i + 1].Y }, { x: controls[i + 2].X, y: controls[i + 2].Y }, { x: controls[i + 3].X, y: controls[i + 3].Y }]; - const newsegs = new Bezier(array).split(t); + this.applyFunction((doc: Doc, ink: InkData) => { + const array = [{ x: controls[i].X, y: controls[i].Y }, { x: controls[i + 1].X, y: controls[i + 1].Y }, { x: controls[i + 2].X, y: controls[i + 2].Y }, { x: controls[i + 3].X, y: controls[i + 3].Y }]; + const newsegs = new Bezier(array).split(t); + controls.splice(i, 4, ...[...newsegs.left.points.map(p => ({ X: p.x, Y: p.y })), ...newsegs.right.points.map(p => ({ X: p.x, Y: p.y }))]); + return controls; + }); // this.applyFunction((doc: Doc, ink: InkData) => { // const newControl = { X: x, Y: y }; // const newPoints: InkData = []; -- cgit v1.2.3-70-g09d2 From 2de1f44d00bcaa5e95045d8b6b7a2c713a72e7b3 Mon Sep 17 00:00:00 2001 From: vkalev Date: Tue, 28 Sep 2021 15:24:42 -0400 Subject: updating broken indices --- src/client/views/InkStrokeProperties.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 6b4cf9e50..adb9a7aa1 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -82,6 +82,21 @@ export class InkStrokeProperties { const array = [{ x: controls[i].X, y: controls[i].Y }, { x: controls[i + 1].X, y: controls[i + 1].Y }, { x: controls[i + 2].X, y: controls[i + 2].Y }, { x: controls[i + 3].X, y: controls[i + 3].Y }]; const newsegs = new Bezier(array).split(t); controls.splice(i, 4, ...[...newsegs.left.points.map(p => ({ X: p.x, Y: p.y })), ...newsegs.right.points.map(p => ({ X: p.x, Y: p.y }))]); + + let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); + // Updating the indices of the control points whose handle tangency has been broken. + if (brokenIndices) { + brokenIndices = new List(brokenIndices.map((control) => { + if (control >= i) { + return control + 4; + } else { + return control; + } + })); + } + doc.brokenInkIndices = brokenIndices; + this._currentPoint = -1; + return controls; }); // this.applyFunction((doc: Doc, ink: InkData) => { @@ -124,19 +139,7 @@ export class InkStrokeProperties { // } // } - // let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); - // // Updating the indices of the control points whose handle tangency has been broken. - // if (brokenIndices) { - // brokenIndices = new List(brokenIndices.map((control) => { - // if (control >= newIndex) { - // return control + 4; - // } else { - // return control; - // } - // })); - // } - // doc.brokenInkIndices = brokenIndices; - // this._currentPoint = -1; + // return newPoints; // }); } -- cgit v1.2.3-70-g09d2 From fd64e2e902fa28f31f81661c863d9f4e574d5d6b Mon Sep 17 00:00:00 2001 From: vkalev Date: Tue, 28 Sep 2021 15:26:38 -0400 Subject: removing previous addpoint code --- src/client/views/InkStrokeProperties.ts | 43 --------------------------------- 1 file changed, 43 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index adb9a7aa1..8821ef5e4 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -99,49 +99,6 @@ export class InkStrokeProperties { return controls; }); - // this.applyFunction((doc: Doc, ink: InkData) => { - // const newControl = { X: x, Y: y }; - // const newPoints: InkData = []; - // let [counter, start, end] = [0, 0, 0]; - // for (let k = 0; k < points.length; k++) { - // if (end === 0) { - // controls.forEach((control) => { - // if (control.X === points[k].X && control.Y === points[k].Y) { - // if (k < index) { - // counter++; - // start = k; - // } else if (k > index) { - // end = k; - // } - // } - // }); - // } - // } - // if (end === 0) end = points.length - 1; - // // Index of new control point with regards to the ink data. - // const newIndex = Math.floor(counter / 2) * 4 + 2; - // // Creating new ink data with the new control point and handle points inputted. - // for (let i = 0; i < ink.length; i++) { - // if (i === newIndex) { - // const [handleA, handleB] = this.getNewHandlePoints(points.slice(start, index + 1), points.slice(index, end), newControl); - // newPoints.push(handleA, newControl, newControl, handleB); - // // Adjusting the magnitude of the left handle line of the right neighboring control point. - // const [rightControl, rightHandle] = [points[end], ink[i]]; - // const scaledVector = this.getScaledHandlePoint(false, start, end, index, rightControl, rightHandle); - // rightHandle && newPoints.push({ X: rightControl.X - scaledVector.X, Y: rightControl.Y - scaledVector.Y }); - // } else if (i === newIndex - 1) { - // // Adjusting the magnitude of the right handle line of the left neighboring control point. - // const [leftControl, leftHandle] = [points[start], ink[i]]; - // const scaledVector = this.getScaledHandlePoint(true, start, end, index, leftControl, leftHandle); - // leftHandle && newPoints.push({ X: leftControl.X - scaledVector.X, Y: leftControl.Y - scaledVector.Y }); - // } else { - // ink[i] && newPoints.push({ X: ink[i].X, Y: ink[i].Y }); - // } - - // } - - // return newPoints; - // }); } /** -- cgit v1.2.3-70-g09d2 From caf4e2e6d14617a4105bd220e8441461c66a2e75 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 15:34:13 -0400 Subject: fixed broken indices for ink curve splitting --- src/client/views/InkStrokeProperties.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 8821ef5e4..115e75c2a 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -87,7 +87,7 @@ export class InkStrokeProperties { // Updating the indices of the control points whose handle tangency has been broken. if (brokenIndices) { brokenIndices = new List(brokenIndices.map((control) => { - if (control >= i) { + if (control > i) { return control + 4; } else { return control; -- cgit v1.2.3-70-g09d2 From cad1445ea3fa81363c00908647ed2825c0f34c65 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 17:21:24 -0400 Subject: fixed adding point drag location. fixed broken indices on deleting. --- src/client/views/InkControls.tsx | 7 ++----- src/client/views/InkStrokeProperties.ts | 16 +++++----------- src/client/views/InkingStroke.tsx | 15 +++++++-------- 3 files changed, 14 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx index 5fe0c0f8a..ee09273e3 100644 --- a/src/client/views/InkControls.tsx +++ b/src/client/views/InkControls.tsx @@ -90,7 +90,6 @@ export class InkControls extends React.Component { const formatInstance = InkStrokeProperties.Instance; if (!formatInstance) return (null); - // Accessing the current ink's data and extracting all control points. const scrData = this.props.screenCtrlPoints; const sreenCtrlPoints: ControlPoint[] = []; @@ -111,12 +110,10 @@ export class InkControls extends React.Component { const nearestScreenPt = this.props.nearestScreenPt(); return ( - {/* should really have just one circle here that represents the neqraest point on the stroke to the users hover point. - This points should be passed as a prop from InkingStroke's UI which should set it in its onPointerOver method */} {!nearestScreenPt ? (null) : ({ X: p.x, Y: p.y })), ...newsegs.right.points.map(p => ({ X: p.x, Y: p.y }))]); - let brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); // Updating the indices of the control points whose handle tangency has been broken. - if (brokenIndices) { - brokenIndices = new List(brokenIndices.map((control) => { - if (control > i) { - return control + 4; - } else { - return control; - } - })); - } - doc.brokenInkIndices = brokenIndices; + doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control > i ? control + 4 : control)); this._currentPoint = -1; return controls; @@ -160,11 +151,14 @@ export class InkStrokeProperties { deletePoints = () => this.applyFunction((doc: Doc, ink: InkData) => { const newPoints: { X: number, Y: number }[] = []; const toRemove = Math.floor(((this._currentPoint + 2) / 4)); + const last = this._currentPoint === ink.length - 1; for (let i = 0; i < ink.length; i++) { if (Math.floor((i + 2) / 4) !== toRemove && (toRemove !== 0 || i > 3)) { newPoints.push({ X: ink[i].X, Y: ink[i].Y }); } } + doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control >= toRemove * 4 ? control - 4 : control)); + if (last) newPoints.splice(newPoints.length - 3, 2); this._currentPoint = -1; if (newPoints.length < 4) return undefined; if (newPoints.length === 4) { diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 83fe04893..40bed0eca 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -141,9 +141,9 @@ export class InkingStroke extends ViewBoxBaseComponent { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth)[0]; - const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(point.X, point.Y)).map(p => ({ X: p[0], Y: p[1] })); + const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, + (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth / 2; const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth / 2; var nearest = Number.MAX_SAFE_INTEGER; @@ -151,16 +151,15 @@ export class InkingStroke extends ViewBoxBaseComponent ({ x: p.X, y: p.Y }))).project({ x: e.clientX, y: e.clientY }); if (point.t) { - const dist = (point.x - screenLeft - e.clientX + screenOrigin[0]) * (point.x - screenLeft - e.clientX + screenOrigin[0]) + - (point.y - screenTop - e.clientY + screenOrigin[1]) * (point.y - screenTop - e.clientY + screenOrigin[1]); + const dist = (point.x - e.clientX) * (point.x - e.clientX) + (point.y - e.clientY) * (point.y - e.clientY); if (dist < nearest) { nearest = dist; this._nearestT = point.t; this._nearestSeg = i; - this._nearestScrPt = { X: point.x, Y: point.y }; + this._nearestScrPt = { X: point.x - screenLeft, Y: point.y - screenTop }; } } } @@ -234,7 +233,7 @@ export class InkingStroke extends ViewBoxBaseComponent - this._nearestScrPt = undefined + console.log("") //this._nearestScrPt = undefined )} onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined} onPointerDown={this.onPointerDown} -- cgit v1.2.3-70-g09d2 From bc654229325e8bbd30c0b3e464c7e66fa0fbc609 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 18:03:07 -0400 Subject: a bunch of code cleanup for inkingstrokes --- src/client/util/InteractionUtils.tsx | 2 - src/client/views/InkControls.tsx | 24 +++---- src/client/views/InkHandles.tsx | 44 ++++++------- src/client/views/InkStrokeProperties.ts | 1 - src/client/views/InkingStroke.tsx | 109 ++++++++++---------------------- 5 files changed, 68 insertions(+), 112 deletions(-) (limited to 'src') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 9f3159d83..3e7a4924f 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,6 +1,4 @@ import React = require("react"); -import * as beziercurve from 'bezier-curve'; -import * as fitCurve from 'fit-curve'; import { Utils } from "../../Utils"; import "./InteractionUtils.scss"; diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx index ee09273e3..eb0eebcdf 100644 --- a/src/client/views/InkControls.tsx +++ b/src/client/views/InkControls.tsx @@ -5,7 +5,7 @@ import { Doc } from "../../fields/Doc"; import { ControlPoint, InkData, PointData } from "../../fields/InkField"; import { listSpec } from "../../fields/Schema"; import { Cast } from "../../fields/Types"; -import { setupMoveUpEvents, Utils } from "../../Utils"; +import { setupMoveUpEvents } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; @@ -15,13 +15,13 @@ export interface InkControlProps { inkDoc: Doc; inkCtrlPoints: InkData; screenCtrlPoints: InkData; - format: number[]; + screenSpaceLineWidth: number; ScreenToLocalTransform: () => Transform; nearestScreenPt: () => PointData | undefined; } @observer -export class InkControls extends React.Component { +export class InkControlPtHandles extends React.Component { @observable private _overControl = -1; @observable private _overAddPoint = -1; @@ -94,18 +94,18 @@ export class InkControls extends React.Component { const scrData = this.props.screenCtrlPoints; const sreenCtrlPoints: ControlPoint[] = []; for (let i = 0; i <= scrData.length - 4; i += 4) { - sreenCtrlPoints.push({ X: scrData[i].X, Y: scrData[i].Y, I: i }); - sreenCtrlPoints.push({ X: scrData[i + 3].X, Y: scrData[i + 3].Y, I: i + 3 }); + sreenCtrlPoints.push({ ...scrData[i], I: i }); + sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 }); } const inkData = this.props.inkCtrlPoints; const inkCtrlPts: ControlPoint[] = []; for (let i = 0; i <= inkData.length - 4; i += 4) { - inkCtrlPts.push({ X: inkData[i].X, Y: inkData[i].Y, I: i }); - inkCtrlPts.push({ X: inkData[i + 3].X, Y: inkData[i + 3].Y, I: i + 3 }); + inkCtrlPts.push({ ...inkData[i], I: i }); + inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 }); } - const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format; + const screenSpaceLineWidth = this.props.screenSpaceLineWidth; const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; const nearestScreenPt = this.props.nearestScreenPt(); @@ -124,18 +124,18 @@ export class InkControls extends React.Component { } {sreenCtrlPoints.map((control, i) => { + onPointerDown={e => { this.changeCurrPoint(control.I); this.onControlDown(e, control.I); }} - onMouseEnter={() => this.onEnterControl(i)} + onMouseEnter={e => this.onEnterControl(i)} onMouseLeave={this.onLeaveControl} pointerEvents="all" cursor="default" diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx index 1a514bdce..dbe9ca027 100644 --- a/src/client/views/InkHandles.tsx +++ b/src/client/views/InkHandles.tsx @@ -14,13 +14,13 @@ import { InkStrokeProperties } from "./InkStrokeProperties"; export interface InkHandlesProps { inkDoc: Doc; - data: InkData; - format: number[]; + screenCtrlPoints: InkData; + screenSpaceLineWidth: number; ScreenToLocalTransform: () => Transform; } @observer -export class InkHandles extends React.Component { +export class InkTangentHandles extends React.Component { /** * Handles the movement of a selected handle point when the user clicks and drags. * @param handleNum The index of the currently selected handle point. @@ -32,8 +32,8 @@ export class InkHandles extends React.Component { const screenScale = this.props.ScreenToLocalTransform().Scale; const order = handleIndex % 4; const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; - const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.data.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.data.length; - const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.data.length; + const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; + const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { if (e.altKey) this.onBreakTangent(controlIndex); InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); @@ -52,10 +52,10 @@ export class InkHandles extends React.Component { onBreakTangent = (controlIndex: number) => { const doc = this.props.inkDoc; if (doc) { - const closed = this.props.data.lastElement().X === this.props.data[0].X && this.props.data.lastElement().Y === this.props.data[0].Y; + const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; if (!brokenIndices?.includes(controlIndex) && - ((controlIndex > 0 && controlIndex < this.props.data.length - 1) || closed)) { + ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { brokenIndices.push(controlIndex); doc.brokenInkIndices = brokenIndices; } @@ -67,14 +67,14 @@ export class InkHandles extends React.Component { if (!formatInstance) return (null); // Accessing the current ink's data and extracting all handle points and handle lines. - const data = this.props.data; + const data = this.props.screenCtrlPoints; const handlePoints: HandlePoint[] = []; const handleLines: HandleLine[] = []; const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; if (data.length >= 4) { for (let i = 0; i <= data.length - 4; i += 4) { - handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); - handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); + handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); + handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); } // Adding first and last (single) handle lines. if (closed) { @@ -88,19 +88,19 @@ export class InkHandles extends React.Component { handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); } } - const [left, top, scaleX, scaleY, strokeWidth, screenSpaceLineWidth] = this.props.format; + const screenSpaceLineWidth = this.props.screenSpaceLineWidth; return ( <> {handlePoints.map((pts, i) => this.onHandleDown(e, pts.I)} + onPointerDown={e => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> @@ -108,18 +108,18 @@ export class InkHandles extends React.Component { {handleLines.map((pts, i) => diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 0b7fe5cd1..cfe6ec523 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -10,7 +10,6 @@ import { DocumentType } from "../documents/DocumentTypes"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; -import { last } from "lodash"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 40bed0eca..c9e401ab4 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,4 +1,5 @@ import React = require("react"); +import { Bezier } from "bezier-js"; import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; @@ -14,9 +15,8 @@ import { InteractionUtils } from "../util/InteractionUtils"; import { ContextMenu } from "./ContextMenu"; import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; -import { InkControls } from "./InkControls"; -import { InkHandles } from "./InkHandles"; -import { Bezier } from "bezier-js"; +import { InkControlPtHandles } from "./InkControls"; +import { InkTangentHandles } from "./InkHandles"; import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; @@ -26,16 +26,21 @@ const InkDocument = makeInterface(documentSchema); @observer export class InkingStroke extends ViewBoxBaseComponent(InkDocument) { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } static readonly MaskDim = 50000; @observable private _properties?: InkStrokeProperties; _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated _selDisposer: IReactionDisposer | undefined; + @observable _nearestT: number | undefined; + @observable _nearestSeg: number | undefined; + @observable _nearestScrPt: { X: number, Y: number } | undefined; + @observable _inkSamplePts: { X: number, Y: number }[] | undefined; + constructor(props: FieldViewProps & InkDocument) { super(props); this._properties = InkStrokeProperties.Instance; - // this._previousColor = ActiveInkColor(); } componentDidMount() { @@ -47,10 +52,6 @@ export class InkingStroke extends ViewBoxBaseComponent { - if (this._handledClick) { - e.stopPropagation(); //stop the event so that docView won't open the lightbox - } - } - /** * Handles the movement of the entire ink object when the user clicks and drags. */ @@ -107,13 +101,10 @@ export class InkingStroke extends ViewBoxBaseComponent { - const inkData: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; + const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1); const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2; const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2; @@ -132,20 +123,13 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt; @action onPointerOver = (e: React.PointerEvent) => { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth)[0]; - const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint((point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, + const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint( + (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); - const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth / 2; - const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth / 2; var nearest = Number.MAX_SAFE_INTEGER; this._nearestT = -1; this._nearestSeg = -1; @@ -159,59 +143,54 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt; componentUI = (boundsLeft: number, boundsTop: number) => { const inkDoc = this.props.Document; const screenSpaceCenterlineStrokeWidth = 3; // the width of the blue line widget that shows the centerline of the ink stroke const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth); - const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(point.X, point.Y)).map(p => ({ X: p[0], Y: p[1] })); - const screenTop = Math.min(...screenPts.map(p => p.Y)) - screenInkWidth[0] / 2; - const screenLeft = Math.min(...screenPts.map(p => p.X)) - screenInkWidth[0] / 2; + const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint( + (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, + (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + const screenHdlPts = screenPts; return
    - {InteractionUtils.CreatePolyline(screenPts, screenLeft, screenTop, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, + {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"), StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker), - StrCast(inkDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false)} - {this._properties?._controlButton ? + StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} + {!this._properties?._controlButton ? (null) : <> - - - : ""} + }
    ; } render() { TraceMobx(); - // Extracting the ink data and formatting information of the current ink stroke. - const inkDoc: Doc = this.layoutDoc; - const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData(); - - const strokeColor = StrCast(this.layoutDoc.color, ""); - const dotsize = Math.max(inkWidth * inkScaleX, inkHeight * inkScaleY) / 40; + const strokeColor = StrCast(this.layoutDoc.color); // Visually renders the polygonal line made by the user. const inkLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, strokeColor, inkStrokeWidth, inkStrokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), StrCast(this.layoutDoc.strokeStartMarker), @@ -232,39 +211,19 @@ export class InkingStroke extends ViewBoxBaseComponent - console.log("") //this._nearestScrPt = undefined - )} + onPointerLeave={action(e => this._nearestScrPt = undefined)} onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined} onPointerDown={this.onPointerDown} - onClick={this.onClick} + onClick={e => this._handledClick && e.stopPropagation()} onContextMenu={() => { const cm = ContextMenu.Instance; - if (cm) { - !Doc.UserDoc().noviceMode && cm.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" }); - cm.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" }); - cm.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" }); - } + !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" }); + cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" }); + cm?.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" }); }} > - {clickableLine} {inkLine} - {/* {this.props.isSelected() ? selectedLine : ""} */} - {/* {this.props.isSelected() && this._properties?._controlButton ? - <> - - - : ""} */} ); } -- cgit v1.2.3-70-g09d2 From 244e06ec9873888dcef3cd08322880d73848fe69 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 18:10:26 -0400 Subject: renamed some ink files --- src/client/util/InteractionUtils.tsx | 4 +- src/client/views/InkControlPtHandles.tsx | 147 +++++++++++++++++++++++++++++++ src/client/views/InkControls.tsx | 147 ------------------------------- src/client/views/InkHandles.tsx | 130 --------------------------- src/client/views/InkTangentHandles.tsx | 130 +++++++++++++++++++++++++++ src/client/views/InkingStroke.tsx | 4 +- 6 files changed, 281 insertions(+), 281 deletions(-) create mode 100644 src/client/views/InkControlPtHandles.tsx delete mode 100644 src/client/views/InkControls.tsx delete mode 100644 src/client/views/InkHandles.tsx create mode 100644 src/client/views/InkTangentHandles.tsx (limited to 'src') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 3e7a4924f..71d68262f 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -98,8 +98,8 @@ export namespace InteractionUtils { if (isNaN(scaley)) scaley = 1; const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `; - var strpts = bezier ? - pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pts[i]) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : + const strpts = bezier ? + pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, ""); const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx new file mode 100644 index 000000000..eb0eebcdf --- /dev/null +++ b/src/client/views/InkControlPtHandles.tsx @@ -0,0 +1,147 @@ +import React = require("react"); +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Doc } from "../../fields/Doc"; +import { ControlPoint, InkData, PointData } from "../../fields/InkField"; +import { listSpec } from "../../fields/Schema"; +import { Cast } from "../../fields/Types"; +import { setupMoveUpEvents } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { UndoManager } from "../util/UndoManager"; +import { Colors } from "./global/globalEnums"; +import { InkStrokeProperties } from "./InkStrokeProperties"; + +export interface InkControlProps { + inkDoc: Doc; + inkCtrlPoints: InkData; + screenCtrlPoints: InkData; + screenSpaceLineWidth: number; + ScreenToLocalTransform: () => Transform; + nearestScreenPt: () => PointData | undefined; +} + +@observer +export class InkControlPtHandles extends React.Component { + + @observable private _overControl = -1; + @observable private _overAddPoint = -1; + /** + * Handles the movement of a selected control point when the user clicks and drags. + * @param controlIndex The index of the currently selected control point. + */ + @action + onControlDown = (e: React.PointerEvent, controlIndex: number): void => { + if (InkStrokeProperties.Instance) { + InkStrokeProperties.Instance.moveControl(0, 0, 1); + const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + const screenScale = this.props.ScreenToLocalTransform().Scale; + const order = controlIndex % 4; + const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; + const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; + const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); + setupMoveUpEvents(this, e, + (e: PointerEvent, down: number[], delta: number[]) => { + InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); + return false; + }, + () => controlUndo?.end(), + action((e: PointerEvent, doubleTap: boolean | undefined) => { + const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; + if (doubleTap && brokenIndices?.includes(equivIndex)) { + InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); + } + if (doubleTap && brokenIndices?.includes(controlIndex)) { + InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + } + })); + } + } + /** + * Updates whether a user has hovered over a particular control point or point that could be added + * on click. + */ + @action onEnterControl = (i: number) => { this._overControl = i; }; + @action onLeaveControl = () => { this._overControl = -1; }; + @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; + @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; + + /** + * Deletes the currently selected point. + */ + @action + onDelete = (e: KeyboardEvent) => { + if (["-", "Backspace", "Delete"].includes(e.key)) { + if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation(); + } + } + + /** + * Changes the current selected control point. + */ + @action + changeCurrPoint = (i: number) => { + if (InkStrokeProperties.Instance) { + InkStrokeProperties.Instance._currentPoint = i; + document.addEventListener("keydown", this.onDelete, true); + } + } + + render() { + const formatInstance = InkStrokeProperties.Instance; + if (!formatInstance) return (null); + + // Accessing the current ink's data and extracting all control points. + const scrData = this.props.screenCtrlPoints; + const sreenCtrlPoints: ControlPoint[] = []; + for (let i = 0; i <= scrData.length - 4; i += 4) { + sreenCtrlPoints.push({ ...scrData[i], I: i }); + sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 }); + } + + const inkData = this.props.inkCtrlPoints; + const inkCtrlPts: ControlPoint[] = []; + for (let i = 0; i <= inkData.length - 4; i += 4) { + inkCtrlPts.push({ ...inkData[i], I: i }); + inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 }); + } + + const screenSpaceLineWidth = this.props.screenSpaceLineWidth; + const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; + + const nearestScreenPt = this.props.nearestScreenPt(); + return ( + {!nearestScreenPt ? (null) : + + } + {sreenCtrlPoints.map((control, i) => + { + this.changeCurrPoint(control.I); + this.onControlDown(e, control.I); + }} + onMouseEnter={e => this.onEnterControl(i)} + onMouseLeave={this.onLeaveControl} + pointerEvents="all" + cursor="default" + /> + )} + + ); + } +} \ No newline at end of file diff --git a/src/client/views/InkControls.tsx b/src/client/views/InkControls.tsx deleted file mode 100644 index eb0eebcdf..000000000 --- a/src/client/views/InkControls.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import React = require("react"); -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../fields/Doc"; -import { ControlPoint, InkData, PointData } from "../../fields/InkField"; -import { listSpec } from "../../fields/Schema"; -import { Cast } from "../../fields/Types"; -import { setupMoveUpEvents } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { UndoManager } from "../util/UndoManager"; -import { Colors } from "./global/globalEnums"; -import { InkStrokeProperties } from "./InkStrokeProperties"; - -export interface InkControlProps { - inkDoc: Doc; - inkCtrlPoints: InkData; - screenCtrlPoints: InkData; - screenSpaceLineWidth: number; - ScreenToLocalTransform: () => Transform; - nearestScreenPt: () => PointData | undefined; -} - -@observer -export class InkControlPtHandles extends React.Component { - - @observable private _overControl = -1; - @observable private _overAddPoint = -1; - /** - * Handles the movement of a selected control point when the user clicks and drags. - * @param controlIndex The index of the currently selected control point. - */ - @action - onControlDown = (e: React.PointerEvent, controlIndex: number): void => { - if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance.moveControl(0, 0, 1); - const controlUndo = UndoManager.StartBatch("DocDecs set radius"); - const screenScale = this.props.ScreenToLocalTransform().Scale; - const order = controlIndex % 4; - const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; - const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; - const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); - setupMoveUpEvents(this, e, - (e: PointerEvent, down: number[], delta: number[]) => { - InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); - return false; - }, - () => controlUndo?.end(), - action((e: PointerEvent, doubleTap: boolean | undefined) => { - const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; - if (doubleTap && brokenIndices?.includes(equivIndex)) { - InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); - } - if (doubleTap && brokenIndices?.includes(controlIndex)) { - InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); - } - })); - } - } - /** - * Updates whether a user has hovered over a particular control point or point that could be added - * on click. - */ - @action onEnterControl = (i: number) => { this._overControl = i; }; - @action onLeaveControl = () => { this._overControl = -1; }; - @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; - @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; - - /** - * Deletes the currently selected point. - */ - @action - onDelete = (e: KeyboardEvent) => { - if (["-", "Backspace", "Delete"].includes(e.key)) { - if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation(); - } - } - - /** - * Changes the current selected control point. - */ - @action - changeCurrPoint = (i: number) => { - if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance._currentPoint = i; - document.addEventListener("keydown", this.onDelete, true); - } - } - - render() { - const formatInstance = InkStrokeProperties.Instance; - if (!formatInstance) return (null); - - // Accessing the current ink's data and extracting all control points. - const scrData = this.props.screenCtrlPoints; - const sreenCtrlPoints: ControlPoint[] = []; - for (let i = 0; i <= scrData.length - 4; i += 4) { - sreenCtrlPoints.push({ ...scrData[i], I: i }); - sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 }); - } - - const inkData = this.props.inkCtrlPoints; - const inkCtrlPts: ControlPoint[] = []; - for (let i = 0; i <= inkData.length - 4; i += 4) { - inkCtrlPts.push({ ...inkData[i], I: i }); - inkCtrlPts.push({ ...inkData[i + 3], I: i + 3 }); - } - - const screenSpaceLineWidth = this.props.screenSpaceLineWidth; - const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; - - const nearestScreenPt = this.props.nearestScreenPt(); - return ( - {!nearestScreenPt ? (null) : - - } - {sreenCtrlPoints.map((control, i) => - { - this.changeCurrPoint(control.I); - this.onControlDown(e, control.I); - }} - onMouseEnter={e => this.onEnterControl(i)} - onMouseLeave={this.onLeaveControl} - pointerEvents="all" - cursor="default" - /> - )} - - ); - } -} \ No newline at end of file diff --git a/src/client/views/InkHandles.tsx b/src/client/views/InkHandles.tsx deleted file mode 100644 index dbe9ca027..000000000 --- a/src/client/views/InkHandles.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React = require("react"); -import { action } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../fields/Doc"; -import { HandleLine, HandlePoint, InkData } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { listSpec } from "../../fields/Schema"; -import { Cast } from "../../fields/Types"; -import { emptyFunction, setupMoveUpEvents } from "../../Utils"; -import { Transform } from "../util/Transform"; -import { UndoManager } from "../util/UndoManager"; -import { Colors } from "./global/globalEnums"; -import { InkStrokeProperties } from "./InkStrokeProperties"; - -export interface InkHandlesProps { - inkDoc: Doc; - screenCtrlPoints: InkData; - screenSpaceLineWidth: number; - ScreenToLocalTransform: () => Transform; -} - -@observer -export class InkTangentHandles extends React.Component { - /** - * Handles the movement of a selected handle point when the user clicks and drags. - * @param handleNum The index of the currently selected handle point. - */ - onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { - if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance.moveControl(0, 0, 1); - const controlUndo = UndoManager.StartBatch("DocDecs set radius"); - const screenScale = this.props.ScreenToLocalTransform().Scale; - const order = handleIndex % 4; - const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; - const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; - const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; - setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { - if (e.altKey) this.onBreakTangent(controlIndex); - InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); - return false; - }, () => controlUndo?.end(), emptyFunction - ); - } - } - - /** - * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and - * its matching (opposite) handle to a list of broken handle indices. - * @param handleNum The index of the currently selected handle point. - */ - @action - onBreakTangent = (controlIndex: number) => { - const doc = this.props.inkDoc; - if (doc) { - const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; - if (!brokenIndices?.includes(controlIndex) && - ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { - brokenIndices.push(controlIndex); - doc.brokenInkIndices = brokenIndices; - } - } - } - - render() { - const formatInstance = InkStrokeProperties.Instance; - if (!formatInstance) return (null); - - // Accessing the current ink's data and extracting all handle points and handle lines. - const data = this.props.screenCtrlPoints; - const handlePoints: HandlePoint[] = []; - const handleLines: HandleLine[] = []; - const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; - if (data.length >= 4) { - for (let i = 0; i <= data.length - 4; i += 4) { - handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); - handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); - } - // Adding first and last (single) handle lines. - if (closed) { - handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); - } - else { - handleLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); - handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); - } - for (let i = 2; i < data.length - 4; i += 4) { - handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); - } - } - const screenSpaceLineWidth = this.props.screenSpaceLineWidth; - - return ( - <> - {handlePoints.map((pts, i) => - - this.onHandleDown(e, pts.I)} - pointerEvents="all" - cursor="default" - display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> - )} - {handleLines.map((pts, i) => - - - - )} - - ); - } -} \ No newline at end of file diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx new file mode 100644 index 000000000..dbe9ca027 --- /dev/null +++ b/src/client/views/InkTangentHandles.tsx @@ -0,0 +1,130 @@ +import React = require("react"); +import { action } from "mobx"; +import { observer } from "mobx-react"; +import { Doc } from "../../fields/Doc"; +import { HandleLine, HandlePoint, InkData } from "../../fields/InkField"; +import { List } from "../../fields/List"; +import { listSpec } from "../../fields/Schema"; +import { Cast } from "../../fields/Types"; +import { emptyFunction, setupMoveUpEvents } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { UndoManager } from "../util/UndoManager"; +import { Colors } from "./global/globalEnums"; +import { InkStrokeProperties } from "./InkStrokeProperties"; + +export interface InkHandlesProps { + inkDoc: Doc; + screenCtrlPoints: InkData; + screenSpaceLineWidth: number; + ScreenToLocalTransform: () => Transform; +} + +@observer +export class InkTangentHandles extends React.Component { + /** + * Handles the movement of a selected handle point when the user clicks and drags. + * @param handleNum The index of the currently selected handle point. + */ + onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { + if (InkStrokeProperties.Instance) { + InkStrokeProperties.Instance.moveControl(0, 0, 1); + const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + const screenScale = this.props.ScreenToLocalTransform().Scale; + const order = handleIndex % 4; + const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; + const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; + const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; + setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { + if (e.altKey) this.onBreakTangent(controlIndex); + InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); + return false; + }, () => controlUndo?.end(), emptyFunction + ); + } + } + + /** + * Breaks tangent handle movement when ‘Alt’ key is held down. Adds the current handle index and + * its matching (opposite) handle to a list of broken handle indices. + * @param handleNum The index of the currently selected handle point. + */ + @action + onBreakTangent = (controlIndex: number) => { + const doc = this.props.inkDoc; + if (doc) { + const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; + const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; + if (!brokenIndices?.includes(controlIndex) && + ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { + brokenIndices.push(controlIndex); + doc.brokenInkIndices = brokenIndices; + } + } + } + + render() { + const formatInstance = InkStrokeProperties.Instance; + if (!formatInstance) return (null); + + // Accessing the current ink's data and extracting all handle points and handle lines. + const data = this.props.screenCtrlPoints; + const handlePoints: HandlePoint[] = []; + const handleLines: HandleLine[] = []; + const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; + if (data.length >= 4) { + for (let i = 0; i <= data.length - 4; i += 4) { + handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); + handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); + } + // Adding first and last (single) handle lines. + if (closed) { + handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); + } + else { + handleLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); + handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); + } + for (let i = 2; i < data.length - 4; i += 4) { + handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); + } + } + const screenSpaceLineWidth = this.props.screenSpaceLineWidth; + + return ( + <> + {handlePoints.map((pts, i) => + + this.onHandleDown(e, pts.I)} + pointerEvents="all" + cursor="default" + display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> + )} + {handleLines.map((pts, i) => + + + + )} + + ); + } +} \ No newline at end of file diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index c9e401ab4..b921014a3 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -15,10 +15,10 @@ import { InteractionUtils } from "../util/InteractionUtils"; import { ContextMenu } from "./ContextMenu"; import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; -import { InkControlPtHandles } from "./InkControls"; -import { InkTangentHandles } from "./InkHandles"; +import { InkControlPtHandles } from "./InkControlPtHandles"; import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; +import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; type InkDocument = makeInterface<[typeof documentSchema]>; -- cgit v1.2.3-70-g09d2 From ca018693224f5f7737f0546c4fa5f4d4210a3020 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 18:33:45 -0400 Subject: prevent crashes when ink points are not multiple of 4. deleting ink ctrl point that leaves one bezier segment no longer converts to a line. --- src/client/util/InteractionUtils.tsx | 2 +- src/client/views/InkStrokeProperties.ts | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 71d68262f..633876683 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -97,7 +97,7 @@ export namespace InteractionUtils { if (isNaN(scalex)) scalex = 1; if (isNaN(scaley)) scaley = 1; - const toScr = (p: { X: number, Y: number }) => ` ${(p.X - left - width / 2) * scalex + width / 2}, ${(p.Y - top - width / 2) * scaley + width / 2} `; + const toScr = (p: { X: number, Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `; const strpts = bezier ? pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, ""); diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index cfe6ec523..510c5f2dd 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -160,14 +160,6 @@ export class InkStrokeProperties { if (last) newPoints.splice(newPoints.length - 3, 2); this._currentPoint = -1; if (newPoints.length < 4) return undefined; - if (newPoints.length === 4) { - const newerPoints: { X: number, Y: number }[] = []; - newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y }); - newerPoints.push({ X: newPoints[0].X, Y: newPoints[0].Y }); - newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y }); - newerPoints.push({ X: newPoints[3].X, Y: newPoints[3].Y }); - return newerPoints; - } return newPoints; }, true) -- cgit v1.2.3-70-g09d2 From faaa10bfeaf9c4a33a75884c3cf93eb3138464d1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Sep 2021 19:17:27 -0400 Subject: cleaned up inkstrokeproperties code a bit. --- src/client/views/InkStrokeProperties.ts | 65 ++++++++++++--------------------- 1 file changed, 23 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 510c5f2dd..6a503cc91 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,6 +1,6 @@ import { Bezier } from "bezier-js"; import { action, computed, observable, reaction } from "mobx"; -import { Doc, Field, Opt } from "../../fields/Doc"; +import { Doc } from "../../fields/Doc"; import { Document } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -29,11 +29,6 @@ export class InkStrokeProperties { return inks.length ? inks : undefined; } - getField(key: string) { - return this.selectedInk?.reduce((p, i) => - (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt); - } - /** * Helper function that enables other functions to be applied to a particular ink instance. * @param func The inputted function. @@ -79,9 +74,10 @@ export class InkStrokeProperties { @action addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => { this.applyFunction((doc: Doc, ink: InkData) => { - const array = [{ x: controls[i].X, y: controls[i].Y }, { x: controls[i + 1].X, y: controls[i + 1].Y }, { x: controls[i + 2].X, y: controls[i + 2].Y }, { x: controls[i + 3].X, y: controls[i + 3].Y }]; - const newsegs = new Bezier(array).split(t); - controls.splice(i, 4, ...[...newsegs.left.points.map(p => ({ X: p.x, Y: p.y })), ...newsegs.right.points.map(p => ({ X: p.x, Y: p.y }))]); + const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]]; + const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t); + const splicepts = [...newsegs.left.points, ...newsegs.right.points]; + controls.splice(i, 4, ...splicepts.map(p => ({ X: p.x, Y: p.y }))); // Updating the indices of the control points whose handle tangency has been broken. doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control > i ? control + 4 : control)); @@ -101,8 +97,7 @@ export class InkStrokeProperties { const prevSize = end - start; const newSize = isLeft ? index - start : end - index; const handleVector = { X: control.X - handle.X, Y: control.Y - handle.Y }; - const scaledVector = { X: handleVector.X * (newSize / prevSize), Y: handleVector.Y * (newSize / prevSize) }; - return scaledVector; + return { X: handleVector.X * (newSize / prevSize), Y: handleVector.Y * (newSize / prevSize) }; } /** @@ -159,8 +154,7 @@ export class InkStrokeProperties { doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control >= toRemove * 4 ? control - 4 : control)); if (last) newPoints.splice(newPoints.length - 3, 2); this._currentPoint = -1; - if (newPoints.length < 4) return undefined; - return newPoints; + return newPoints.length < 4 ? undefined : newPoints; }, true) /** @@ -174,11 +168,11 @@ export class InkStrokeProperties { const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 }; - const newPoints: { X: number, Y: number }[] = []; - ink.map(i => ({ X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y })).forEach(i => { - const newX = Math.cos(angle) * i.X - Math.sin(angle) * i.Y; - const newY = Math.sin(angle) * i.X + Math.cos(angle) * i.Y; - newPoints.push({ X: newX + centerPoint.X, Y: newY + centerPoint.Y }); + const newPoints = ink.map(i => { + const pt = { X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y }; + const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y; + const newY = Math.sin(angle) * pt.X + Math.cos(angle) * pt.Y; + return { X: newX + centerPoint.X, Y: newY + centerPoint.Y }; }); doc.rotation = NumCast(doc.rotation) + angle; return newPoints; @@ -192,10 +186,10 @@ export class InkStrokeProperties { @action moveControl = (deltaX: number, deltaY: number, controlIndex: number) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { - const newPoints: { X: number, Y: number }[] = []; const order = controlIndex % 4; const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; - for (var i = 0; i < ink.length; i++) { + + return ink.map((pt, i) => { const leftHandlePoint = order === 0 && i === controlIndex + 1; const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; if (controlIndex === i || @@ -207,12 +201,10 @@ export class InkStrokeProperties { (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) { - newPoints.push({ X: ink[i].X - deltaX / xScale, Y: ink[i].Y - deltaY / yScale }); - } else { - newPoints.push({ X: ink[i].X, Y: ink[i].Y }); + return ({ X: pt.X - deltaX / xScale, Y: pt.Y - deltaY / yScale }); } - } - return newPoints; + return pt; + }); }) /** @@ -224,18 +216,11 @@ export class InkStrokeProperties { this.applyFunction((doc: Doc, ink: InkData) => { const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); if (brokenIndices) { - const newBrokenIndices = new List; - brokenIndices.forEach(brokenIndex => { - if (brokenIndex !== controlIndex) { - newBrokenIndices.push(brokenIndex); - } - }); - doc.brokenInkIndices = newBrokenIndices; + doc.brokenInkIndices = new List(brokenIndices.filter(brokenIndex => brokenIndex !== controlIndex)); const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]]; const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI); const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint); - const newHandleB = this.rotatePoint(handleB, controlPoint, angleDifference); - ink[handleIndexB] = newHandleB; + ink[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference); return ink; } }); @@ -249,9 +234,7 @@ export class InkStrokeProperties { const rotatedTarget = { X: target.X - origin.X, Y: target.Y - origin.Y }; const newX = Math.cos(angle) * rotatedTarget.X - Math.sin(angle) * rotatedTarget.Y; const newY = Math.sin(angle) * rotatedTarget.X + Math.cos(angle) * rotatedTarget.Y; - rotatedTarget.X = newX + origin.X; - rotatedTarget.Y = newY + origin.Y; - return rotatedTarget; + return { X: newX + origin.X, Y: newY + origin.Y }; } /** @@ -265,8 +248,7 @@ export class InkStrokeProperties { // Normalizing the vectors. vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA }; vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB }; - const dotProduct = vectorB.X * vectorA.X + vectorB.Y * vectorA.Y; - return Math.acos(dotProduct); + return Math.acos(vectorB.X * vectorA.X + vectorB.Y * vectorA.Y); } /** @@ -292,7 +274,7 @@ export class InkStrokeProperties { this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; const oldHandlePoint = ink[handleIndex]; - let oppositeHandlePoint = ink[oppositeHandleIndex]; + const oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; ink[handleIndex] = newHandlePoint; @@ -300,8 +282,7 @@ export class InkStrokeProperties { // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); - oppositeHandlePoint = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); - ink[oppositeHandleIndex] = oppositeHandlePoint; + ink[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); } return ink; }) -- cgit v1.2.3-70-g09d2 From aed57a2d6435007676409aeba562fc11d0c4a44d Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 01:38:15 -0400 Subject: a number of undo/redo fixes for ink (snapping to tangent, add points, dragging tangents). also tried to make storage of undo events more efficient when dragging ink controls (avoid saving hundreds of copies of the InkField) --- src/client/util/UndoManager.ts | 19 +++++++++++++++++++ src/client/views/DocumentDecorations.tsx | 5 ++++- src/client/views/InkControlPtHandles.tsx | 27 ++++++++++++++++----------- src/client/views/InkStrokeProperties.ts | 19 +++++++++++-------- src/client/views/InkTangentHandles.tsx | 24 ++++++++++++------------ src/client/views/InkingStroke.tsx | 8 ++++---- src/fields/util.ts | 13 ++++++------- 7 files changed, 72 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 05fb9f378..536d90371 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -70,6 +70,7 @@ export namespace UndoManager { export interface UndoEvent { undo: () => void; redo: () => void; + prop: string; } type UndoBatch = UndoEvent[]; @@ -104,6 +105,23 @@ export namespace UndoManager { export function GetOpenBatches(): Without[] { return openBatches; } + export function FilterBatches(fieldTypes: string[]) { + var fieldCounts: { [key: string]: number } = {}; + const lastStack = UndoManager.undoStack.lastElement(); + if (lastStack) { + lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1)); + var fieldCount2: { [key: string]: number } = {}; + runInAction(() => + UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => { + if (fieldTypes.includes(ev.prop)) { + fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1; + if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true; + return false; + } + return true; + })); + } + } export function TraceOpenBatches() { console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join("\n\t")}\n`); } @@ -140,6 +158,7 @@ export namespace UndoManager { batchCounter--; // console.log("End " + batchCounter); if (batchCounter === 0 && currentBatch?.length) { + // console.log("------ended----") if (!cancel) { undoStack.push(currentBatch); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index bd61c9f60..bd9c3509b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -202,7 +202,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180)); return false; }, - () => this._rotateUndo?.end(), + () => { + this._rotateUndo?.end(); + UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); + }, emptyFunction); this._prevY = e.clientY; this._inkCenterPts = SelectionManager.Views() diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index eb0eebcdf..898c3bf26 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -24,7 +24,6 @@ export interface InkControlProps { export class InkControlPtHandles extends React.Component { @observable private _overControl = -1; - @observable private _overAddPoint = -1; /** * Handles the movement of a selected control point when the user clicks and drags. * @param controlIndex The index of the currently selected control point. @@ -32,8 +31,7 @@ export class InkControlPtHandles extends React.Component { @action onControlDown = (e: React.PointerEvent, controlIndex: number): void => { if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance.moveControl(0, 0, 1); - const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + var controlUndo: UndoManager.Batch | undefined; const screenScale = this.props.ScreenToLocalTransform().Scale; const order = controlIndex % 4; const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; @@ -41,17 +39,26 @@ export class InkControlPtHandles extends React.Component { const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { + if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); return false; }, - () => controlUndo?.end(), + () => { + controlUndo?.end(); + UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); + }, action((e: PointerEvent, doubleTap: boolean | undefined) => { const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; - if (doubleTap && brokenIndices?.includes(equivIndex)) { - InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); - } - if (doubleTap && brokenIndices?.includes(controlIndex)) { - InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + if (doubleTap) { + if (brokenIndices?.includes(equivIndex)) { + if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth"); + InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); + } + if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { + if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth"); + InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + } + controlUndo?.end(); } })); } @@ -62,8 +69,6 @@ export class InkControlPtHandles extends React.Component { */ @action onEnterControl = (i: number) => { this._overControl = i; }; @action onLeaveControl = () => { this._overControl = -1; }; - @action onEnterAddPoint = (i: number) => { this._overAddPoint = i; }; - @action onLeaveAddPoint = () => { this._overAddPoint = -1; }; /** * Deletes the currently selected point. diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 6a503cc91..3770eb7c1 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -214,14 +214,16 @@ export class InkStrokeProperties { */ snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => { this.applyFunction((doc: Doc, ink: InkData) => { - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); - if (brokenIndices) { - doc.brokenInkIndices = new List(brokenIndices.filter(brokenIndex => brokenIndex !== controlIndex)); + const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []); + const ind = brokenIndices.findIndex(value => value === controlIndex); + if (ind !== -1) { + brokenIndices.splice(ind, 1); const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]]; const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI); const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint); - ink[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference); - return ink; + const inkCopy = ink.slice(); + inkCopy[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference); + return inkCopy; } }); } @@ -277,13 +279,14 @@ export class InkStrokeProperties { const oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; - ink[handleIndex] = newHandlePoint; + const inkCopy = ink.slice(); + inkCopy[handleIndex] = newHandlePoint; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); - ink[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); + inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); } - return ink; + return inkCopy; }) } \ No newline at end of file diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index dbe9ca027..759e48134 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -27,18 +27,21 @@ export class InkTangentHandles extends React.Component { */ onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { if (InkStrokeProperties.Instance) { - InkStrokeProperties.Instance.moveControl(0, 0, 1); - const controlUndo = UndoManager.StartBatch("DocDecs set radius"); + var controlUndo: UndoManager.Batch | undefined; const screenScale = this.props.ScreenToLocalTransform().Scale; const order = handleIndex % 4; const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length; setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { + if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent"); if (e.altKey) this.onBreakTangent(controlIndex); InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); return false; - }, () => controlUndo?.end(), emptyFunction + }, () => { + controlUndo?.end(); + UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); + }, emptyFunction ); } } @@ -50,15 +53,12 @@ export class InkTangentHandles extends React.Component { */ @action onBreakTangent = (controlIndex: number) => { - const doc = this.props.inkDoc; - if (doc) { - const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")) || new List; - if (!brokenIndices?.includes(controlIndex) && - ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { - brokenIndices.push(controlIndex); - doc.brokenInkIndices = brokenIndices; - } + const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; + var brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); + if (!brokenIndices?.includes(controlIndex) && + ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { + if (brokenIndices) brokenIndices.push(controlIndex); + else this.props.inkDoc.brokenInkIndices = new List([controlIndex]); } } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index b921014a3..867677005 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -77,8 +77,8 @@ export class InkingStroke extends ViewBoxBaseComponent { + onPointerMove = (e: React.PointerEvent) => { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint( (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, @@ -212,7 +212,7 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt = undefined)} - onPointerMove={this.props.isSelected() ? this.onPointerOver : undefined} + onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined} onPointerDown={this.onPointerDown} onClick={e => this._handledClick && e.stopPropagation()} onContextMenu={() => { diff --git a/src/fields/util.ts b/src/fields/util.ts index 3590c2dea..99dfc04d9 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -98,13 +98,12 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number } else { DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); } - !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && UndoManager.AddEvent({ - redo: () => receiver[prop] = value, - undo: () => { - // console.log("Undo: " + prop + " = " + curValue); // bcz: uncomment to log undo - receiver[prop] = curValue; - } - }); + !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && + UndoManager.AddEvent({ + redo: () => receiver[prop] = value, + undo: () => receiver[prop] = curValue, + prop: prop?.toString() + }); return true; } return false; -- cgit v1.2.3-70-g09d2 From 048f22e2c5a2254591aad4719a8efd6357d16430 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 09:37:34 -0400 Subject: fixed ink controls to be above document decorations --- src/client/views/InkStroke.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss index 53d27cd24..e876d11ed 100644 --- a/src/client/views/InkStroke.scss +++ b/src/client/views/InkStroke.scss @@ -3,6 +3,7 @@ position: absolute; overflow: visible; pointer-events: none; + z-index: 2001; // 1 higher than documentdecorations svg:not(:root) { overflow: visible !important; -- cgit v1.2.3-70-g09d2 From e5905220a84a62fff36965a3bf74a55b793ae31b Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 11:26:58 -0400 Subject: fixed filling of curves. added toggling of brokenindex with right-click. changed look of ink handles to be lighter weight and to reflect brokenindex sttate --- src/client/util/InteractionUtils.tsx | 2 +- src/client/views/InkControlPtHandles.tsx | 95 ++++++++++++++++++-------------- src/client/views/InkTangentHandles.tsx | 55 +++++++++--------- src/client/views/InkingStroke.tsx | 1 + 4 files changed, 82 insertions(+), 71 deletions(-) (limited to 'src') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 633876683..d43ebd692 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -99,7 +99,7 @@ export namespace InteractionUtils { const toScr = (p: { X: number, Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `; const strpts = bezier ? - pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : "M" + toScr(pt) + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : + pts.reduce((acc: string, pt, i) => acc + (i % 4 !== 0 ? "" : (i === 0 ? "M" + toScr(pt) : "") + "C" + toScr(pts[i + 1]) + toScr(pts[i + 2]) + toScr(pts[i + 3])), "") : pts.reduce((acc: string, pt) => acc + `${toScr(pt)} `, ""); const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 898c3bf26..f80aca268 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -10,6 +10,7 @@ import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; import { InkStrokeProperties } from "./InkStrokeProperties"; +import { List } from "../../fields/List"; export interface InkControlProps { inkDoc: Doc; @@ -24,6 +25,8 @@ export interface InkControlProps { export class InkControlPtHandles extends React.Component { @observable private _overControl = -1; + + @observable controlUndo: UndoManager.Batch | undefined; /** * Handles the movement of a selected control point when the user clicks and drags. * @param controlIndex The index of the currently selected control point. @@ -31,34 +34,40 @@ export class InkControlPtHandles extends React.Component { @action onControlDown = (e: React.PointerEvent, controlIndex: number): void => { if (InkStrokeProperties.Instance) { - var controlUndo: UndoManager.Batch | undefined; const screenScale = this.props.ScreenToLocalTransform().Scale; const order = controlIndex % 4; const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); setupMoveUpEvents(this, e, - (e: PointerEvent, down: number[], delta: number[]) => { - if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); + action((e: PointerEvent, down: number[], delta: number[]) => { + if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); return false; - }, - () => { - controlUndo?.end(); + }), + action(() => { + this.controlUndo?.end(); + this.controlUndo = undefined; UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); - }, + }), action((e: PointerEvent, doubleTap: boolean | undefined) => { const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; - if (doubleTap) { - if (brokenIndices?.includes(equivIndex)) { - if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); - } - if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { - if (!controlUndo) controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + if (doubleTap || e.button === 2) { + if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) { + if (brokenIndices) brokenIndices.push(controlIndex); + else this.props.inkDoc.brokenInkIndices = new List([controlIndex]); + } else { + if (brokenIndices?.includes(equivIndex)) { + if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); + InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); + } + if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { + if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); + InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + } } - controlUndo?.end(); + this.controlUndo?.end(); + this.controlUndo = undefined; } })); } @@ -92,9 +101,6 @@ export class InkControlPtHandles extends React.Component { } render() { - const formatInstance = InkStrokeProperties.Instance; - if (!formatInstance) return (null); - // Accessing the current ink's data and extracting all control points. const scrData = this.props.screenCtrlPoints; const sreenCtrlPoints: ControlPoint[] = []; @@ -111,41 +117,46 @@ export class InkControlPtHandles extends React.Component { } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; - const rectHdlSize = (i: number) => this._overControl === i ? screenSpaceLineWidth * 6 : screenSpaceLineWidth * 4; - const nearestScreenPt = this.props.nearestScreenPt(); + const TagType = (broken?: boolean) => broken ? "rect" : "circle"; + const hdl = (control: { X: number, Y: number, I: number }, scale: number, color: string) => { + const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"))?.includes(control.I); + const Tag = TagType(broken) as keyof JSX.IntrinsicElements; + return { + this.changeCurrPoint(control.I); + this.onControlDown(e, control.I); + }} + onMouseEnter={() => this.onEnterControl(control.I)} + onMouseLeave={this.onLeaveControl} + pointerEvents="all" + cursor="default" + />; + } return ( {!nearestScreenPt ? (null) : } - {sreenCtrlPoints.map((control, i) => - { - this.changeCurrPoint(control.I); - this.onControlDown(e, control.I); - }} - onMouseEnter={e => this.onEnterControl(i)} - onMouseLeave={this.onLeaveControl} - pointerEvents="all" - cursor="default" - /> - )} + {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))} ); } diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 759e48134..4f1a406c2 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -68,62 +68,61 @@ export class InkTangentHandles extends React.Component { // Accessing the current ink's data and extracting all handle points and handle lines. const data = this.props.screenCtrlPoints; - const handlePoints: HandlePoint[] = []; - const handleLines: HandleLine[] = []; + const tangentHandles: HandlePoint[] = []; + const tangentLines: HandleLine[] = []; const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; if (data.length >= 4) { for (let i = 0; i <= data.length - 4; i += 4) { - handlePoints.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); - handlePoints.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); + tangentHandles.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); + tangentHandles.push({ ...data[i + 2], I: i + 2, dot1: i + 3, dot2: i === data.length ? (closed ? (i + 4) % data.length : i + 3) : i + 4 }); } // Adding first and last (single) handle lines. if (closed) { - handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); + tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: data.length - 1 }); } else { - handleLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); - handleLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); + tangentLines.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); + tangentLines.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); } for (let i = 2; i < data.length - 4; i += 4) { - handleLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); + tangentLines.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); } } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; return ( <> - {handlePoints.map((pts, i) => + {tangentHandles.map((pts, i) => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} /> )} - {handleLines.map((pts, i) => - - + {tangentLines.map((pts, i) => { + const tangentLine = (x1: number, y1: number, x2: number, y2: number) => - )} + strokeDasharray={"1 1"} + strokeWidth={1} + display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />; + return + {tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)} + {tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)} + ; + })} ); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 867677005..4cd7f7698 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -210,6 +210,7 @@ export class InkingStroke extends ViewBoxBaseComponent this._nearestScrPt = undefined)} onPointerMove={this.props.isSelected() ? this.onPointerMove : undefined} -- cgit v1.2.3-70-g09d2 From e6451eda7c7a5be73922b302627c53db5e22d474 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 12:06:32 -0400 Subject: support drawing of closed ink shapes. fixed highlight state of pen button on startup --- src/client/views/GestureOverlay.tsx | 7 +++++-- src/client/views/StyleProvider.tsx | 4 +--- src/client/views/nodes/button/FontIconBox.tsx | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 465e6b2df..a95660409 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -627,6 +627,9 @@ export class GestureOverlay extends Touchable { } + const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)); + if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; this._points = controlPoints; this.dispatchGesture(GestureUtils.Gestures.Stroke); @@ -761,10 +764,10 @@ export class GestureOverlay extends Touchable { break; case "line": - if (Math.abs(firstx - lastx) < 20) { + if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) { lastx = firstx; } - if (Math.abs(firsty - lasty) < 20) { + if (Math.abs(firsty - lasty) < 10 && Math.abs(firstx - lastx) > 10) { lasty = firsty; } this._points.push({ X: firstx, Y: firsty }); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index cd6e11bda..e86a877f6 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -181,13 +181,11 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 && ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ?
    toggleBackground(doc)}> - +
    : (null); } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index f975b063a..33fa23805 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -755,7 +755,7 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolean) { if (checkResult) { - return (Doc.UserDoc().activeInkTool === tool && GestureOverlay.Instance?.InkShape === "" || GestureOverlay.Instance?.InkShape === tool) ? + return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ? Colors.MEDIUM_BLUE : "transparent"; } if (["circle", "square", "line"].includes(tool)) { -- cgit v1.2.3-70-g09d2 From 5f95911a504a47c867198fccc32a75bf22d26056 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 15:15:21 -0400 Subject: added snapping to close curve or to self-snap a vertex to its curve. fixed ink decorations from being clipped when zoomed. fixed crash with zero-length tangent --- src/client/views/InkControlPtHandles.tsx | 18 ++++++---- src/client/views/InkStrokeProperties.ts | 61 ++++++++++++++++++++++++++++++-- src/client/views/InkingStroke.tsx | 26 ++++---------- src/client/views/MainView.tsx | 2 +- 4 files changed, 78 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index f80aca268..249a0fa64 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -42,10 +42,13 @@ export class InkControlPtHandles extends React.Component { setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); - InkStrokeProperties.Instance?.moveControl(-delta[0] * screenScale, -delta[1] * screenScale, controlIndex); + InkStrokeProperties.Instance?.moveControl(delta[0] * screenScale, delta[1] * screenScale, controlIndex); return false; }), action(() => { + if (this.controlUndo) { + InkStrokeProperties.Instance?.snapControl(this.props.inkDoc, controlIndex); + } this.controlUndo?.end(); this.controlUndo = undefined; UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); @@ -117,20 +120,21 @@ export class InkControlPtHandles extends React.Component { } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; + const closed = inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; const nearestScreenPt = this.props.nearestScreenPt(); const TagType = (broken?: boolean) => broken ? "rect" : "circle"; const hdl = (control: { X: number, Y: number, I: number }, scale: number, color: string) => { const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"))?.includes(control.I); - const Tag = TagType(broken) as keyof JSX.IntrinsicElements; - return { + const newpts = ink.map((pt, i) => { const leftHandlePoint = order === 0 && i === controlIndex + 1; const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; if (controlIndex === i || @@ -201,12 +201,68 @@ export class InkStrokeProperties { (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) { - return ({ X: pt.X - deltaX / xScale, Y: pt.Y - deltaY / yScale }); + return ({ X: pt.X + deltaX / xScale, Y: pt.Y + deltaY / yScale }); } return pt; }); + return newpts; }) + + public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refPt: { X: number, Y: number }, excludeSegs?: number[]) { + var distance = Number.MAX_SAFE_INTEGER; + var nearestT = -1; + var nearestSeg = -1; + var nearestPt = { X: 0, Y: 0 }; + for (var i = 0; i < ctrlPoints.length - 3; i += 4) { + if (excludeSegs?.includes(i)) continue; + const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]]; + const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refPt.X, y: refPt.Y }); + if (point.t !== undefined) { + const dist = Math.sqrt((point.x - refPt.X) * (point.x - refPt.X) + (point.y - refPt.Y) * (point.y - refPt.Y)); + if (dist < distance) { + distance = dist; + nearestT = point.t; + nearestSeg = i; + nearestPt = { X: point.x, Y: point.y }; + } + } + } + return { distance, nearestT, nearestSeg, nearestPt }; + } + + /** + * Handles the movement/scaling of a control point. + */ + snapControl = (inkDoc: Doc, controlIndex: number) => { + const ink = Cast(inkDoc.data, InkField)?.inkData; + if (ink) { + const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; + + // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve) + const thisseg = Math.floor(controlIndex / 4) * 4; + const which = controlIndex % 4; + const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1; + const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1; + const refPt = ink[controlIndex]; + const { nearestPt } = InkStrokeProperties.nearestPtToStroke(ink, refPt, [thisseg, prevseg, nextseg]); + + // nearestPt is in inkDoc coordinates -- we need to compute the distance in screen coordinates. + // so we scale the X & Y distances by the internal ink scale factor and then transform the final distance by the ScreenToLocal.Scale of the inkDoc itself. + const oldXrange = (xs => ({ coord: NumCast(inkDoc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); + const oldYrange = (ys => ({ coord: NumCast(inkDoc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); + const ptsXscale = NumCast(inkDoc._width) / (oldXrange.max - oldXrange.min); + const ptsYscale = NumCast(inkDoc._height) / (oldYrange.max - oldYrange.min); + const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale + + (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); + + if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) { + return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex) + } + } + return false; + } + /** * Snaps a control point with broken tangency back to synced rotation. * @param handleIndexA The handle point that retains its current position. @@ -247,6 +303,7 @@ export class InkStrokeProperties { angleBetweenTwoVectors = (vectorA: PointData, vectorB: PointData) => { const magnitudeA = Math.sqrt(vectorA.X * vectorA.X + vectorA.Y * vectorA.Y); const magnitudeB = Math.sqrt(vectorB.X * vectorB.X + vectorB.Y * vectorB.Y); + if (magnitudeA === 0 || magnitudeB === 0) return 0; // Normalizing the vectors. vectorA = { X: vectorA.X / magnitudeA, Y: vectorA.Y / magnitudeA }; vectorB = { X: vectorB.X / magnitudeB, Y: vectorB.Y / magnitudeB }; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 4cd7f7698..8b1b3ea32 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -130,25 +130,14 @@ export class InkingStroke extends ViewBoxBaseComponent this.props.ScreenToLocalTransform().inverse().transformPoint( (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); - var nearest = Number.MAX_SAFE_INTEGER; - this._nearestT = -1; - this._nearestSeg = -1; - this._nearestScrPt = { X: 0, Y: 0 }; - for (var i = 0; i < screenPts.length - 3; i += 4) { - const array = [screenPts[i], screenPts[i + 1], screenPts[i + 2], screenPts[i + 3]]; - const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: e.clientX, y: e.clientY }); - if (point.t) { - const dist = (point.x - e.clientX) * (point.x - e.clientX) + (point.y - e.clientY) * (point.y - e.clientY); - if (dist < nearest) { - nearest = dist; - this._nearestT = point.t; - this._nearestSeg = i; - this._nearestScrPt = { X: point.x, Y: point.y }; - } - } - } + const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); + + this._nearestT = nearestT; + this._nearestSeg = nearestSeg; + this._nearestScrPt = nearestPt; } + nearestScreenPt = () => this._nearestScrPt; componentUI = (boundsLeft: number, boundsTop: number) => { const inkDoc = this.props.Document; @@ -159,11 +148,10 @@ export class InkingStroke extends ViewBoxBaseComponent this.props.ScreenToLocalTransform().inverse().transformPoint( (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); - const screenOrigin = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); const screenHdlPts = screenPts; return
    {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"), diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 3f7df705f..6d0d5eb39 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -618,7 +618,7 @@ export class MainView extends React.Component { - + {this.topbar} {LinkDescriptionPopup.descriptionPopup ? : null} {DocumentLinksButton.LinkEditorDocView ? : (null)} -- cgit v1.2.3-70-g09d2 From c7121eb1756e04eafd6a74e665ef46ada655df5a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 22:13:29 -0400 Subject: cleaned up/fixed line arrowheads & dots. added linecap options for lines, added outline of lines using fillColor. turned off border in comic mode for ink. --- src/client/util/InteractionUtils.tsx | 33 +++++++++++++++++---------------- src/client/views/GestureOverlay.tsx | 9 ++++++--- src/client/views/InkingStroke.tsx | 32 +++++++++++++++++++++++--------- src/client/views/StyleProvider.tsx | 2 +- 4 files changed, 47 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index d43ebd692..48fb968cf 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -90,7 +90,7 @@ export namespace InteractionUtils { } export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, - color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, + color: string, width: number, strokeWidth: number, lineJoin: string, lineCap: string, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean) { const pts = shape ? makePolygon(shape, points) : points; @@ -104,20 +104,23 @@ export namespace InteractionUtils { const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; const defGuid = Utils.GenerateGuid(); - const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth))); const Tag = (bezier ? "path" : "polyline") as keyof JSX.IntrinsicElements; + const makerStrokeWidth = strokeWidth / 2; return ( {/* setting the svg fill sets the arrowStart fill */} {nodefs ? (null) : - {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : - - } - {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : - - } - {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : - - } + {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : + + + } + {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : + + + } + {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : + + + } } - {/* {addables} */} ); } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index a95660409..f28485e43 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -841,20 +841,23 @@ export class GestureOverlay extends Touchable { B.bottom = B.bottom + width / 2; B.width += width; B.height += width; + const fillColor = ActiveFillColor(); + const strokeColor = fillColor && fillColor !== "transparent" ? fillColor : ActiveInkColor(); return [ this.props.children, this._palette, [this._strokes.map((l, i) => { const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 };//this.getBounds(l, true); return - {InteractionUtils.CreatePolyline(l, b.left, b.top, ActiveInkColor(), width, width, - ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), + {InteractionUtils.CreatePolyline(l, b.left, b.top, strokeColor, width, width, "miter", "round", + ActiveInkBezierApprox(), "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} ; }), this._points.length <= 1 ? (null) : - {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "", ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} + {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "miter", "round", "", + "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} ] ]; } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 8b1b3ea32..952af3019 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -20,6 +20,7 @@ import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; +import { SnappingManager } from "../util/SnappingManager"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -143,6 +144,7 @@ export class InkingStroke extends ViewBoxBaseComponent this.props.ScreenToLocalTransform().inverse().transformPoint( @@ -150,13 +152,15 @@ export class InkingStroke extends ViewBoxBaseComponent ({ X: p[0], Y: p[1] })); const screenHdlPts = screenPts; - return
    {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, - StrCast(inkDoc.strokeBezier), StrCast(inkDoc.fillColor, "none"), - StrCast(inkDoc.strokeStartMarker), StrCast(inkDoc.strokeEndMarker), - StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} + StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier), + !closed ? "none" : StrCast(this.layoutDoc.fillColor, "none"), + startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} {!this._properties?._controlButton ? (null) : <> , props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; -- cgit v1.2.3-70-g09d2 From b9adc2fec10d4509cd96558bb832f7c02bc70b25 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 22:26:24 -0400 Subject: fixed breaking smoothness of closed curve at 0/last index. fixed highlighting closed curves. --- src/client/views/InkStrokeProperties.ts | 4 +++- src/client/views/InkingStroke.tsx | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index ac5cdfee2..d5033f44b 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -339,8 +339,10 @@ export class InkStrokeProperties { const inkCopy = ink.slice(); inkCopy[handleIndex] = newHandlePoint; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); + const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). - if ((!brokenIndices || !brokenIndices?.includes(controlIndex)) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { + if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && + (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 952af3019..552318e23 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -159,8 +159,7 @@ export class InkingStroke extends ViewBoxBaseComponent {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier), - !closed ? "none" : StrCast(this.layoutDoc.fillColor, "none"), - startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} + "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} {!this._properties?._controlButton ? (null) : <> Date: Wed, 29 Sep 2021 22:51:21 -0400 Subject: made IsClosed a static function. fixed warnings and errors. --- src/client/util/UndoManager.ts | 4 ++-- src/client/views/InkControlPtHandles.tsx | 5 +++-- src/client/views/InkStrokeProperties.ts | 9 +++++---- src/client/views/InkTangentHandles.tsx | 7 ++++--- src/client/views/InkingStroke.tsx | 6 ++++-- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/fields/util.ts | 9 ++++++--- 8 files changed, 26 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 536d90371..44e44d335 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -106,11 +106,11 @@ export namespace UndoManager { return openBatches; } export function FilterBatches(fieldTypes: string[]) { - var fieldCounts: { [key: string]: number } = {}; + const fieldCounts: { [key: string]: number } = {}; const lastStack = UndoManager.undoStack.lastElement(); if (lastStack) { lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1)); - var fieldCount2: { [key: string]: number } = {}; + const fieldCount2: { [key: string]: number } = {}; runInAction(() => UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => { if (fieldTypes.includes(ev.prop)) { diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 249a0fa64..8162f3fdc 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -11,6 +11,7 @@ import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { List } from "../../fields/List"; +import { InkingStroke } from "./InkingStroke"; export interface InkControlProps { inkDoc: Doc; @@ -120,7 +121,7 @@ export class InkControlPtHandles extends React.Component { } const screenSpaceLineWidth = this.props.screenSpaceLineWidth; - const closed = inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; + const closed = InkingStroke.IsClosed(inkData); const nearestScreenPt = this.props.nearestScreenPt(); const TagType = (broken?: boolean) => broken ? "rect" : "circle"; const hdl = (control: { X: number, Y: number, I: number }, scale: number, color: string) => { @@ -147,7 +148,7 @@ export class InkControlPtHandles extends React.Component { pointerEvents="all" cursor="default" />; - } + }; return ( {!nearestScreenPt ? (null) : this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const order = controlIndex % 4; - const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; + const closed = InkingStroke.IsClosed(ink); const newpts = ink.map((pt, i) => { const leftHandlePoint = order === 0 && i === controlIndex + 1; @@ -237,7 +238,7 @@ export class InkStrokeProperties { snapControl = (inkDoc: Doc, controlIndex: number) => { const ink = Cast(inkDoc.data, InkField)?.inkData; if (ink) { - const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; + const closed = InkingStroke.IsClosed(ink); // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve) const thisseg = Math.floor(controlIndex / 4) * 4; @@ -257,7 +258,7 @@ export class InkStrokeProperties { (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) { - return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex) + return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); } } return false; @@ -331,7 +332,7 @@ export class InkStrokeProperties { @action moveHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { - const closed = ink.lastElement().X === ink[0].X && ink.lastElement().Y === ink[0].Y; + const closed = InkingStroke.IsClosed(ink); const oldHandlePoint = ink[handleIndex]; const oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 4f1a406c2..8f7bef936 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -11,6 +11,7 @@ import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; import { InkStrokeProperties } from "./InkStrokeProperties"; +import { InkingStroke } from "./InkingStroke"; export interface InkHandlesProps { inkDoc: Doc; @@ -53,8 +54,8 @@ export class InkTangentHandles extends React.Component { */ @action onBreakTangent = (controlIndex: number) => { - const closed = this.props.screenCtrlPoints.lastElement().X === this.props.screenCtrlPoints[0].X && this.props.screenCtrlPoints.lastElement().Y === this.props.screenCtrlPoints[0].Y; - var brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); + const closed = InkingStroke.IsClosed(this.props.screenCtrlPoints); + const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); if (!brokenIndices?.includes(controlIndex) && ((controlIndex > 0 && controlIndex < this.props.screenCtrlPoints.length - 1) || closed)) { if (brokenIndices) brokenIndices.push(controlIndex); @@ -70,7 +71,7 @@ export class InkTangentHandles extends React.Component { const data = this.props.screenCtrlPoints; const tangentHandles: HandlePoint[] = []; const tangentLines: HandleLine[] = []; - const closed = data.lastElement().X === data[0].X && data.lastElement().Y === data[0].Y; + const closed = InkingStroke.IsClosed(data); if (data.length >= 4) { for (let i = 0; i <= data.length - 4; i += 4) { tangentHandles.push({ ...data[i + 1], I: i + 1, dot1: i, dot2: i === 0 ? (closed ? data.length - 1 : i) : i - 1 }); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 552318e23..42a09fa52 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -29,6 +29,9 @@ const InkDocument = makeInterface(documentSchema); export class InkingStroke extends ViewBoxBaseComponent(InkDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } static readonly MaskDim = 50000; + public static IsClosed(inkData: InkData) { + return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; + } @observable private _properties?: InkStrokeProperties; _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated _selDisposer: IReactionDisposer | undefined; @@ -144,7 +147,6 @@ export class InkingStroke extends ViewBoxBaseComponent this.props.ScreenToLocalTransform().inverse().transformPoint( @@ -184,7 +186,7 @@ export class InkingStroke extends ViewBoxBaseComponent void; Pause?: () => void; setFocus?: () => void; - componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element; + componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; fieldKey?: string; annotationKey?: string; getTitle?: () => string; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 7afa54d5f..aa53f751d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -960,7 +960,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp GoogleApiClientUtils.Docs.write({ reference, content, mode }); } }; - UndoManager.AddEvent({ undo, redo }); + UndoManager.AddEvent({ undo, redo, prop: "" }); redo(); }); } diff --git a/src/fields/util.ts b/src/fields/util.ts index 99dfc04d9..c708affe3 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -405,7 +405,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any ind !== -1 && receiver[prop].splice(ind, 1); }); lastValue = ObjectField.MakeCopy(receiver[prop]); - }) + }), + prop: "" } : diff?.op === "$remFromSet" ? { @@ -423,7 +424,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item); }); lastValue = ObjectField.MakeCopy(receiver[prop]); - } + }, + prop: "" } : { redo: () => { @@ -434,7 +436,8 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo receiver[prop] = ObjectField.MakeCopy(prevValue as List); lastValue = ObjectField.MakeCopy(receiver[prop]); - } + }, + prop: "" }); } target[Update](op); -- cgit v1.2.3-70-g09d2 From 21e559d699c2828f83e58a048f2aad6634abdc07 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 29 Sep 2021 23:50:54 -0400 Subject: fixed placement of link icon for ink strokes. --- src/client/views/InkStroke.scss | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss index e876d11ed..55e06c6ca 100644 --- a/src/client/views/InkStroke.scss +++ b/src/client/views/InkStroke.scss @@ -19,6 +19,8 @@ stroke-linecap: round; overflow: visible !important; transform-origin: top left; + width: 100%; + height: 100%; svg:not(:root) { overflow: visible !important; -- cgit v1.2.3-70-g09d2 From 54e8ec7fe41c5a88eb3d9639c5350ac9d7b3e814 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 08:42:39 -0400 Subject: went back to solid tangent handles --- src/client/views/InkTangentHandles.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 8f7bef936..9e37e005b 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -99,10 +99,9 @@ export class InkTangentHandles extends React.Component { cx={pts.X} cy={pts.Y} r={screenSpaceLineWidth * 2} - fill={"transparent"} + fill={Colors.MEDIUM_BLUE} strokeWidth={1} stroke={Colors.MEDIUM_BLUE} - strokeDasharray={"1 1"} onPointerDown={e => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" -- cgit v1.2.3-70-g09d2 From 6263cd58a46acf57eb6a9508b118adc8bad2be37 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 09:57:16 -0400 Subject: critical fix for creating new accounts without hanging. --- src/client/documents/Documents.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 403620bdd..f2db25559 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -506,12 +506,14 @@ export namespace Docs { const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix); // fetch the actual prototype documents from the server const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds); - Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeHeightUnfrozen = true; // to avoid having to recreate the DB - Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeHeightUnfrozen = true; - Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeHeightUnfrozen = true; - Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeDimModifiable = true; // to avoid having to recreate the DB - Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeDimModifiable = true; - Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeDimModifiable = true; + if (!Docs.newAccount) { + Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeHeightUnfrozen = true; // to avoid having to recreate the DB + Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeHeightUnfrozen = true; + Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeHeightUnfrozen = true; + Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeDimModifiable = true; // to avoid having to recreate the DB + Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeDimModifiable = true; + Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeDimModifiable = true; + } // update this object to include any default values: DocumentOptions for all prototypes prototypeIds.map(id => { -- cgit v1.2.3-70-g09d2 From 4ccaecc185bed251ee74d1d9168cf3602c969680 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 10:20:02 -0400 Subject: fixed dragging lines with arrows to display arrowhead --- src/client/util/DragManager.ts | 16 ++++++++++++++++ src/client/util/InteractionUtils.tsx | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 421e4c6bb..6fde4b06e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -365,6 +365,22 @@ export namespace DragManager { const dragElements = eles.map(ele => { if (!ele.parentNode) dragDiv.appendChild(ele); const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement; + let children = Array.from(dragElement.children); + while (children.length) { // need to replace all the maker node reference ids with new unique ids. otherwise, the clone nodes will reference the original nodes which are all hidden during the drag + const next = children.pop(); + next && children.push(...Array.from(next.children)); + if (next) { + if (next.localName.startsWith("path") && (next.attributes as any)["marker-start"]) { + next.setAttribute("marker-start", (next.attributes as any)["marker-start"].value.replace("#", "#X")); + } + if (next.localName.startsWith("path") && (next.attributes as any)["marker-end"]) { + next.setAttribute("marker-end", (next.attributes as any)["marker-end"].value.replace("#", "#X")); + } + if (next.localName.startsWith("marker")) { + next.id = "X" + next.id; + } + } + } const rect = ele.getBoundingClientRect(); const scaleX = rect.width / ele.offsetWidth; const scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX; diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 48fb968cf..a32a8eecc 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -113,11 +113,11 @@ export namespace InteractionUtils { } - {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : + {arrowStart !== "arrow" ? (null) : } - {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : + {arrowEnd !== "arrow" ? (null) : } -- cgit v1.2.3-70-g09d2 From 479a2a8e004e1b0e8711fc670b7a9146b3039dfa Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 10:23:13 -0400 Subject: from last --- src/client/util/DragManager.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 6fde4b06e..858f67273 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -370,12 +370,11 @@ export namespace DragManager { const next = children.pop(); next && children.push(...Array.from(next.children)); if (next) { - if (next.localName.startsWith("path") && (next.attributes as any)["marker-start"]) { - next.setAttribute("marker-start", (next.attributes as any)["marker-start"].value.replace("#", "#X")); - } - if (next.localName.startsWith("path") && (next.attributes as any)["marker-end"]) { - next.setAttribute("marker-end", (next.attributes as any)["marker-end"].value.replace("#", "#X")); - } + ["marker-start", "marker-mid", "marker-end"].forEach(field => { + if (next.localName.startsWith("path") && (next.attributes as any)[field]) { + next.setAttribute(field, (next.attributes as any)[field].value.replace("#", "#X")); + } + }); if (next.localName.startsWith("marker")) { next.id = "X" + next.id; } -- cgit v1.2.3-70-g09d2 From 065e9acb9243b31f0d15498cf9c46539accd593d Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 12:03:54 -0400 Subject: fixed context menu for ink from docbuttons bar --- src/client/views/DocumentButtonBar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 7f428a881..8edd7e5bd 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -336,9 +336,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV openContextMenu = (e: React.MouseEvent) => { let child = SelectionManager.Views()[0].ContentDiv!.children[0]; while (child.children.length) { - const next = Array.from(child.children).find(c => typeof (c.className) === "string"); - if (next?.className.includes(DocumentView.ROOT_DIV)) break; - if (next?.className.includes("dashFieldView")) break; + const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string"); + if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; + if (next?.className?.toString().includes("dashFieldView")) break; if (next) child = next; else break; } -- cgit v1.2.3-70-g09d2 From 4e4a1ec7bb6c479e8fd1b0a7bfe73e2447bafc74 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 13:11:26 -0400 Subject: fixed creating/drawing straight horizontal/vertical lines. fixed showing proper context menu for ink. --- src/client/views/DocumentButtonBar.tsx | 3 ++- src/client/views/InkControlPtHandles.tsx | 2 +- src/client/views/InkStrokeProperties.ts | 12 ++++++------ src/client/views/InkTangentHandles.tsx | 2 +- src/client/views/InkingStroke.tsx | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 8edd7e5bd..aa9318310 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -28,6 +28,7 @@ import { PresBox } from './nodes/trails/PresBox'; import { undoBatch } from '../util/UndoManager'; import { CollectionViewType } from './collections/CollectionView'; import { Colors } from './global/globalEnums'; +import { DashFieldView } from './nodes/formattedText/DashFieldView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -338,7 +339,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV while (child.children.length) { const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string"); if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; - if (next?.className?.toString().includes("dashFieldView")) break; + if (next?.className?.toString().includes(DashFieldView.name)) break; if (next) child = next; else break; } diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 8162f3fdc..8eb74381a 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -43,7 +43,7 @@ export class InkControlPtHandles extends React.Component { setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); - InkStrokeProperties.Instance?.moveControl(delta[0] * screenScale, delta[1] * screenScale, controlIndex); + InkStrokeProperties.Instance?.moveControlPtHandle(delta[0] * screenScale, delta[1] * screenScale, controlIndex); return false; }), action(() => { diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 72912ff20..00201fa56 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -45,8 +45,8 @@ export class InkStrokeProperties { if (ink) { const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const ptsXscale = NumCast(doc._width) / (oldXrange.max - oldXrange.min); - const ptsYscale = NumCast(doc._height) / (oldYrange.max - oldYrange.min); + const ptsXscale = NumCast(doc._width) / ((oldXrange.max - oldXrange.min) || 1); + const ptsYscale = NumCast(doc._height) / ((oldYrange.max - oldYrange.min) || 1); const newPoints = func(doc, ink, ptsXscale, ptsYscale); if (newPoints) { const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X)); @@ -185,7 +185,7 @@ export class InkStrokeProperties { */ @undoBatch @action - moveControl = (deltaX: number, deltaY: number, controlIndex: number) => + moveControlPtHandle = (deltaX: number, deltaY: number, controlIndex: number) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const order = controlIndex % 4; const closed = InkingStroke.IsClosed(ink); @@ -258,7 +258,7 @@ export class InkStrokeProperties { (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) { - return this.moveControl((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); + return this.moveControlPtHandle((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); } } return false; @@ -278,7 +278,7 @@ export class InkStrokeProperties { const [controlPoint, handleA, handleB] = [ink[controlIndex], ink[handleIndexA], ink[handleIndexB]]; const oppositeHandleA = this.rotatePoint(handleA, controlPoint, Math.PI); const angleDifference = this.angleChange(handleB, oppositeHandleA, controlPoint); - const inkCopy = ink.slice(); + const inkCopy = ink.slice(); // have to make a new copy of the array to keep from corrupting undo/redo. without slicing, the same array will be stored in each undo step meaning earlier undo steps will be inadvertently updated to store the latest value. inkCopy[handleIndexB] = this.rotatePoint(handleB, controlPoint, angleDifference); return inkCopy; } @@ -330,7 +330,7 @@ export class InkStrokeProperties { */ @undoBatch @action - moveHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => + moveTangentHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { const closed = InkingStroke.IsClosed(ink); const oldHandlePoint = ink[handleIndex]; diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 9e37e005b..df5bebf31 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -37,7 +37,7 @@ export class InkTangentHandles extends React.Component { setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent"); if (e.altKey) this.onBreakTangent(controlIndex); - InkStrokeProperties.Instance?.moveHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); + InkStrokeProperties.Instance?.moveTangentHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); return false; }, () => { controlUndo?.end(); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 42a09fa52..5438350d8 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -123,8 +123,8 @@ export class InkingStroke extends ViewBoxBaseComponent Date: Thu, 30 Sep 2021 13:58:59 -0400 Subject: fixed jitter of position when moving ink points around --- src/client/views/InkStrokeProperties.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 00201fa56..68169f246 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -45,14 +45,14 @@ export class InkStrokeProperties { if (ink) { const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const ptsXscale = NumCast(doc._width) / ((oldXrange.max - oldXrange.min) || 1); - const ptsYscale = NumCast(doc._height) / ((oldYrange.max - oldYrange.min) || 1); + const ptsXscale = ((NumCast(doc._width) - NumCast(doc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; + const ptsYscale = ((NumCast(doc._height) - NumCast(doc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; const newPoints = func(doc, ink, ptsXscale, ptsYscale); if (newPoints) { const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X)); const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y)); - doc._width = (newXrange.max - newXrange.min) * ptsXscale; - doc._height = (newYrange.max - newYrange.min) * ptsYscale; + doc._width = (newXrange.max - newXrange.min) * ptsXscale + NumCast(doc.strokeWidth); + doc._height = (newYrange.max - newYrange.min) * ptsYscale + NumCast(doc.strokeWidth); doc.x = (oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale); doc.y = (oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale); Doc.GetProto(doc).data = new InkField(newPoints); -- cgit v1.2.3-70-g09d2 From c0423cc9c04e87bb4bff79d2d1bcdd0612efb1a2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 14:59:06 -0400 Subject: dont select ink control point on drag -- only with an explicit click. toggle ink control point selection with clicks. --- src/client/views/InkControlPtHandles.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 8eb74381a..94c238b2b 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -40,6 +40,7 @@ export class InkControlPtHandles extends React.Component { const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); + const wasSelected = InkStrokeProperties.Instance?._currentPoint === controlIndex; setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); @@ -73,7 +74,8 @@ export class InkControlPtHandles extends React.Component { this.controlUndo?.end(); this.controlUndo = undefined; } - })); + this.changeCurrPoint(controlIndex); + }), undefined, undefined, () => wasSelected && this.changeCurrPoint(-1)); } } /** @@ -139,10 +141,7 @@ export class InkControlPtHandles extends React.Component { strokeWidth={screenSpaceLineWidth / 2} stroke={Colors.MEDIUM_BLUE} fill={broken ? Colors.MEDIUM_BLUE : color} - onPointerDown={(e: any) => { - this.changeCurrPoint(control.I); - this.onControlDown(e, control.I); - }} + onPointerDown={(e: any) => this.onControlDown(e, control.I)} onMouseEnter={() => this.onEnterControl(control.I)} onMouseLeave={this.onLeaveControl} pointerEvents="all" -- cgit v1.2.3-70-g09d2 From c457efd3d57359c285bd00a65c9fdce7a27e27f6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 16:06:27 -0400 Subject: fixed ink rotation --- src/client/views/InkStrokeProperties.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 68169f246..ef4976b77 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -171,8 +171,8 @@ export class InkStrokeProperties { const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 }; const newPoints = ink.map(i => { const pt = { X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y }; - const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y; - const newY = Math.sin(angle) * pt.X + Math.cos(angle) * pt.Y; + const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale; + const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y; return { X: newX + centerPoint.X, Y: newY + centerPoint.Y }; }); doc.rotation = NumCast(doc.rotation) + angle; -- cgit v1.2.3-70-g09d2 From 24014ab70a7ef8294239addbea21ced76861b435 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Sep 2021 17:10:32 -0400 Subject: fixed warning --- src/client/util/DragManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 858f67273..f5704d2bf 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -365,7 +365,7 @@ export namespace DragManager { const dragElements = eles.map(ele => { if (!ele.parentNode) dragDiv.appendChild(ele); const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement; - let children = Array.from(dragElement.children); + const children = Array.from(dragElement.children); while (children.length) { // need to replace all the maker node reference ids with new unique ids. otherwise, the clone nodes will reference the original nodes which are all hidden during the drag const next = children.pop(); next && children.push(...Array.from(next.children)); -- cgit v1.2.3-70-g09d2 From cc5294616936c5e8a3a09f51fba4e6e89ce2c26c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 1 Oct 2021 11:30:45 -0400 Subject: fixed keyboard delete of ink control points to never delete the stroke. --- src/client/views/InkControlPtHandles.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 94c238b2b..0644488b3 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -28,6 +28,13 @@ export class InkControlPtHandles extends React.Component { @observable private _overControl = -1; @observable controlUndo: UndoManager.Batch | undefined; + + componentDidMount() { + document.addEventListener("keydown", this.onDelete, true); + } + componentWillUnmount() { + document.removeEventListener("keydown", this.onDelete, true); + } /** * Handles the movement of a selected control point when the user clicks and drags. * @param controlIndex The index of the currently selected control point. @@ -91,7 +98,8 @@ export class InkControlPtHandles extends React.Component { @action onDelete = (e: KeyboardEvent) => { if (["-", "Backspace", "Delete"].includes(e.key)) { - if (InkStrokeProperties.Instance?.deletePoints()) e.stopPropagation(); + InkStrokeProperties.Instance?.deletePoints(); + e.stopPropagation(); } } @@ -102,7 +110,6 @@ export class InkControlPtHandles extends React.Component { changeCurrPoint = (i: number) => { if (InkStrokeProperties.Instance) { InkStrokeProperties.Instance._currentPoint = i; - document.addEventListener("keydown", this.onDelete, true); } } -- cgit v1.2.3-70-g09d2 From d4c435da7f4c291b444c2a7da9afa195c5f0dcac Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 1 Oct 2021 13:16:45 -0400 Subject: fixed ink to not have a bounding box when isLinkButton is set but rather to have an outline of the ink stroke --- src/client/views/InkingStroke.tsx | 8 ++++++-- src/client/views/StyleProvider.tsx | 2 +- src/client/views/global/globalCssVariables.scss | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 5438350d8..ff90563e1 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -21,6 +21,7 @@ import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; import { SnappingManager } from "../util/SnappingManager"; +import Color = require("color"); type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -199,8 +200,11 @@ export class InkingStroke extends ViewBoxBaseComponent, props: Opt Date: Fri, 1 Oct 2021 13:32:08 -0400 Subject: fixed crash / errors with comparison box --- src/client/views/nodes/ComparisonBox.scss | 1 + src/client/views/nodes/ComparisonBox.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index 44cf5d046..660045a6f 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -6,6 +6,7 @@ position: relative; z-index: 0; pointer-events: none; + display: flex; .clip-div { position: absolute; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 72c7e4f45..15c33c8bf 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -89,7 +89,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { var whichDoc = Cast(this.dataDoc[which], Doc, null); - if (whichDoc.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null); + if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null); return whichDoc ? <> Date: Fri, 1 Oct 2021 14:58:16 -0400 Subject: added stroke highlighting when following links. turned off display of centerline stroke when stroke is just selected but control point editing is not active. fixed snapping ink to make closed curves. --- src/client/views/InkStrokeProperties.ts | 4 ++-- src/client/views/InkingStroke.tsx | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index ef4976b77..ee30caa3d 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -252,8 +252,8 @@ export class InkStrokeProperties { // so we scale the X & Y distances by the internal ink scale factor and then transform the final distance by the ScreenToLocal.Scale of the inkDoc itself. const oldXrange = (xs => ({ coord: NumCast(inkDoc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); const oldYrange = (ys => ({ coord: NumCast(inkDoc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const ptsXscale = NumCast(inkDoc._width) / (oldXrange.max - oldXrange.min); - const ptsYscale = NumCast(inkDoc._height) / (oldYrange.max - oldYrange.min); + const ptsXscale = ((NumCast(inkDoc._width) - NumCast(inkDoc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; + const ptsYscale = ((NumCast(inkDoc._height) - NumCast(inkDoc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale + (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index ff90563e1..d05a4a6e4 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -6,7 +6,7 @@ import { Doc } from "../../fields/Doc"; import { documentSchema } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool } from "../../fields/InkField"; import { makeInterface } from "../../fields/Schema"; -import { Cast, NumCast, StrCast } from "../../fields/Types"; +import { Cast, NumCast, StrCast, BoolCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; @@ -160,11 +160,11 @@ export class InkingStroke extends ViewBoxBaseComponent - {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, - StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier), - "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} {!this._properties?._controlButton ? (null) : <> + {InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, + StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier), + "none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} Date: Mon, 4 Oct 2021 15:55:37 -0400 Subject: fixed dropping onto comparison box. added caption of link description to link comparison view. added promise wait for protos of link anchors to avoid potential crash. made dragging a link off of a linkmenuitem to create an alias with the hidden flag removed. --- src/client/documents/Documents.ts | 5 +++-- src/client/util/LinkManager.ts | 18 +++++++++++------- src/client/views/linking/LinkMenuItem.tsx | 4 +++- src/client/views/nodes/ComparisonBox.tsx | 4 ++-- 4 files changed, 19 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f2db25559..e3623c069 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -165,6 +165,7 @@ export class DocumentOptions { version?: string; // version identifier for a document label?: string; hidden?: boolean; + _hidden?: boolean; mediaState?: string; // status of media document: "pendingRecording", "recording", "paused", "playing" autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. @@ -401,7 +402,7 @@ export namespace Docs { [DocumentType.LINK, { layout: { view: LinkBox, dataField: defaultDataKey }, options: { - childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", + childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", showCaption: "description", backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area links: ComputedField.MakeFunction("links(self)") as any, _removeDropProperties: new List(["_layerTags", "isLinkButton"]), @@ -1103,7 +1104,7 @@ export namespace DocUtils { "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, linkDisplay: true, - hidden: true, + _hidden: true, linkRelationship, _showCaption: "description", _showTitle: "linkRelationship", diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 64da68f59..53bd13fb3 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -38,15 +38,19 @@ export class LinkManager { const addLinkToDoc = (link: Doc) => { const a1Prom = link?.anchor1; const a2Prom = link?.anchor2; - Promise.all([a1Prom, a2Prom]).then(action((all) => { + Promise.all([a1Prom, a2Prom]).then((all) => { const a1 = all[0]; const a2 = all[1]; - if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { - Doc.GetProto(a1)[DirectLinksSym].add(link); - Doc.GetProto(a2)[DirectLinksSym].add(link); - Doc.GetProto(link)[DirectLinksSym].add(link); - } - })); + const a1ProtoProm = (link?.anchor1 as Doc)?.proto; + const a2ProtoProm = (link?.anchor2 as Doc)?.proto; + Promise.all([a1ProtoProm, a2ProtoProm]).then(action((all) => { + if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { + Doc.GetProto(a1)[DirectLinksSym].add(link); + Doc.GetProto(a2)[DirectLinksSym].add(link); + Doc.GetProto(link)[DirectLinksSym].add(link); + } + })); + }); }; const remLinkFromDoc = (link: Doc) => { const a1 = link?.anchor1; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 53a7ae9ab..728956378 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -79,7 +79,9 @@ export class LinkMenuItem extends React.Component { onEdit = (e: React.PointerEvent): void => { LinkManager.currentLink = this.props.linkDoc; setupMoveUpEvents(this, e, e => { - DragManager.StartDocumentDrag([this._editRef.current!], new DragManager.DocumentDragData([this.props.linkDoc]), e.x, e.y); + const dragData = new DragManager.DocumentDragData([this.props.linkDoc], "alias"); + dragData.removeDropProperties = ["hidden"]; + DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y); return true; }, emptyFunction, () => this.props.showEditor(this.props.linkDoc)); } diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 15c33c8bf..f23c68409 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -50,7 +50,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent, targetWidth: number) => { - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, action(() => { + e.button !== 2 && setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, action(() => { // on click, animate slider movement to the targetWidth this._animating = "all 200ms"; this.layoutDoc._clipWidth = targetWidth * 100 / this.props.PanelWidth(); @@ -107,7 +107,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { return
    this.registerSliding(e, cover)} - ref={ele => this.createDropTarget(ele, `compareBox-${which}`, index)} > + ref={ele => this.createDropTarget(ele, which, index)} > {displayDoc(which)}
    ; }; -- cgit v1.2.3-70-g09d2 From fc679d849ae8afa3ef66e4e0b2b2b816e1fb41d4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 4 Oct 2021 21:21:55 -0400 Subject: fixed how filters work in Schema View. made filtering the default for clicking in title area. --- .../views/collections/CollectionSchemaView.tsx | 575 --------------------- .../collectionSchema/CollectionSchemaHeaders.tsx | 72 ++- .../collectionSchema/CollectionSchemaView.tsx | 2 +- .../collections/collectionSchema/SchemaTable.tsx | 14 +- src/fields/Doc.ts | 2 +- 5 files changed, 40 insertions(+), 625 deletions(-) delete mode 100644 src/client/views/collections/CollectionSchemaView.tsx (limited to 'src') diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx deleted file mode 100644 index 3ea190a98..000000000 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ /dev/null @@ -1,575 +0,0 @@ -import React = require("react"); -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, untracked } from "mobx"; -import { observer } from "mobx-react"; -import Measure from "react-measure"; -import { Resize } from "react-table"; -import "react-table/react-table.css"; -import { Doc, Opt } from "../../../fields/Doc"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { Cast, NumCast } from "../../../fields/Types"; -import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils"; -import { DocUtils } from "../../documents/Documents"; -import { SelectionManager } from "../../util/SelectionManager"; -import { SnappingManager } from "../../util/SnappingManager"; -import { Transform } from "../../util/Transform"; -import { undoBatch } from "../../util/UndoManager"; -import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/global/globalCssVariables.scss'; -import { SchemaTable } from "../collections/collectionSchema/SchemaTable"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import '../DocumentDecorations.scss'; -import { DocumentView } from "../nodes/DocumentView"; -import { DefaultStyleProvider } from "../StyleProvider"; -import "./CollectionSchemaView.scss"; -import { CollectionSubView } from "./CollectionSubView"; -// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 - -export enum ColumnType { - Any, - Number, - String, - Boolean, - Doc, - Image, - List, - Date -} -// this map should be used for keys that should have a const type of value -const columnTypes: Map = new Map([ - ["title", ColumnType.String], - ["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number], - ["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean], - ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number] -]); - -@observer -export class CollectionSchemaView extends CollectionSubView(doc => doc) { - private _previewCont?: HTMLDivElement; - - @observable _previewDoc: Doc | undefined = undefined; - @observable _focusedTable: Doc = this.props.Document; - @observable _col: any = ""; - @observable _menuWidth = 0; - @observable _headerOpen = false; - @observable _headerIsEditing = false; - @observable _menuHeight = 0; - @observable _pointerX = 0; - @observable _pointerY = 0; - @observable _openTypes: boolean = false; - - @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); } - @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; } - @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); } - @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } - @computed get scale() { return this.props.ScreenToLocalTransform().Scale; } - @computed get columns() { return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []); } - set columns(columns: SchemaHeaderField[]) { this.props.Document._schemaHeaders = new List(columns); } - - @computed get menuCoordinates() { - let searchx = 0; - let searchy = 0; - if (this.props.Document._searchDoc) { - const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0]; - if (el !== undefined) { - const rect = el.getBoundingClientRect(); - searchx = rect.x; - searchy = rect.y; - } - } - const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx; - const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy; - return this.props.ScreenToLocalTransform().transformPoint(x, y); - } - - get documentKeys() { - const docs = this.childDocs; - const keys: { [key: string]: boolean } = {}; - // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. - // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be - // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. - // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu - // is displayed (unlikely) it won't show up until something else changes. - //TODO Types - untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false)))); - - this.columns.forEach(key => keys[key.heading] = true); - return Array.from(Object.keys(keys)); - } - - @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing; - - @undoBatch - setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => { - this._openTypes = false; - if (columnTypes.get(columnField.heading)) return; - - const columns = this.columns; - const index = columns.indexOf(columnField); - if (index > -1) { - columnField.setType(NumCast(type)); - columns[index] = columnField; - this.columns = columns; - } - }); - - @undoBatch - setColumnColor = (columnField: SchemaHeaderField, color: string): void => { - const columns = this.columns; - const index = columns.indexOf(columnField); - if (index > -1) { - columnField.setColor(color); - columns[index] = columnField; - this.columns = columns; // need to set the columns to trigger rerender - } - } - - @undoBatch - @action - setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => { - const columns = this.columns; - columns.forEach(col => col.setDesc(undefined)); - - const index = columns.findIndex(c => c.heading === columnField.heading); - const column = columns[index]; - column.setDesc(descending); - columns[index] = column; - this.columns = columns; - } - - renderTypes = (col: any) => { - if (columnTypes.get(col.heading)) return (null); - - const type = col.type; - - const anyType =
    this.setColumnType(col, ColumnType.Any)}> - - Any -
    ; - - const numType =
    this.setColumnType(col, ColumnType.Number)}> - - Number -
    ; - - const textType =
    this.setColumnType(col, ColumnType.String)}> - - Text -
    ; - - const boolType =
    this.setColumnType(col, ColumnType.Boolean)}> - - Checkbox -
    ; - - const listType =
    this.setColumnType(col, ColumnType.List)}> - - List -
    ; - - const docType =
    this.setColumnType(col, ColumnType.Doc)}> - - Document -
    ; - - const imageType =
    this.setColumnType(col, ColumnType.Image)}> - - Image -
    ; - - const dateType =
    this.setColumnType(col, ColumnType.Date)}> - - Date -
    ; - - - const allColumnTypes =
    - {anyType} - {numType} - {textType} - {boolType} - {listType} - {docType} - {imageType} - {dateType} -
    ; - - const justColType = type === ColumnType.Any ? anyType : type === ColumnType.Number ? numType : - type === ColumnType.String ? textType : type === ColumnType.Boolean ? boolType : - type === ColumnType.List ? listType : type === ColumnType.Doc ? docType : - type === ColumnType.Date ? dateType : imageType; - - return ( -
    this._openTypes = !this._openTypes)}> -
    - - -
    - {this._openTypes ? allColumnTypes : justColType} -
    - ); - } - - renderSorting = (col: any) => { - const sort = col.desc; - return ( -
    - -
    -
    this.setColumnSort(col, true)}> - - Sort descending -
    -
    this.setColumnSort(col, false)}> - - Sort ascending -
    -
    this.setColumnSort(col, undefined)}> - - Clear sorting -
    -
    -
    - ); - } - - renderColors = (col: any) => { - const selected = col.color; - - const pink = PastelSchemaPalette.get("pink2"); - const purple = PastelSchemaPalette.get("purple2"); - const blue = PastelSchemaPalette.get("bluegreen1"); - const yellow = PastelSchemaPalette.get("yellow4"); - const red = PastelSchemaPalette.get("red2"); - const gray = "#f1efeb"; - - return ( -
    - -
    -
    this.setColumnColor(col, pink!)}>
    -
    this.setColumnColor(col, purple!)}>
    -
    this.setColumnColor(col, blue!)}>
    -
    this.setColumnColor(col, yellow!)}>
    -
    this.setColumnColor(col, red!)}>
    -
    this.setColumnColor(col, gray)}>
    -
    -
    - ); - } - - @undoBatch - @action - changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => { - const columns = this.columns; - if (columns === undefined) { - this.columns = new List([new SchemaHeaderField(newKey, "f1efeb")]); - } else { - if (addNew) { - columns.push(new SchemaHeaderField(newKey, "f1efeb")); - this.columns = columns; - } else { - const index = columns.map(c => c.heading).indexOf(oldKey); - if (index > -1) { - const column = columns[index]; - column.setHeading(newKey); - columns[index] = column; - this.columns = columns; - if (filter) { - Doc.setDocFilter(this.props.Document, newKey, filter, "match"); - } - else { - this.props.Document._docFilters = undefined; - } - } - } - } - } - - @action - openHeader = (col: any, screenx: number, screeny: number) => { - this._col = col; - this._headerOpen = true; - this._pointerX = screenx; - this._pointerY = screeny; - } - - @action - closeHeader = () => { this._headerOpen = false; } - - @undoBatch - @action - deleteColumn = (key: string) => { - const columns = this.columns; - if (columns === undefined) { - this.columns = new List([]); - } else { - const index = columns.map(c => c.heading).indexOf(key); - if (index > -1) { - columns.splice(index, 1); - this.columns = columns; - } - } - this.closeHeader(); - } - - getPreviewTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(- this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, - this.borderWidth); - } - - @action - onHeaderClick = (e: React.PointerEvent) => { - e.stopPropagation(); - } - - @action - onWheel(e: React.WheelEvent) { - const scale = this.props.ScreenToLocalTransform().Scale; - this.props.isContentActive(true) && e.stopPropagation(); - } - - @computed get renderMenuContent() { - TraceMobx(); - return
    - {this.renderTypes(this._col)} - {this.renderColors(this._col)} -
    - -
    -
    ; - } - - private createTarget = (ele: HTMLDivElement) => { - this._previewCont = ele; - super.CreateDropTarget(ele); - } - - isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable; - - @action setFocused = (doc: Doc) => this._focusedTable = doc; - - @action setPreviewDoc = (doc: Opt) => { - SelectionManager.SelectSchemaViewDoc(doc); - this._previewDoc = doc; - } - - //toggles preview side-panel of schema - @action - toggleExpander = () => { - this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0; - } - - onDividerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander); - } - @action - onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { - const nativeWidth = this._previewCont!.getBoundingClientRect(); - const minWidth = 40; - const maxWidth = 1000; - const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]; - const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth; - this.props.Document.schemaPreviewWidth = width; - return false; - } - - onPointerDown = (e: React.PointerEvent): void => { - if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (this.props.isSelected(true)) e.stopPropagation(); - else this.props.select(false); - } - } - - @computed - get previewDocument(): Doc | undefined { return this._previewDoc; } - - @computed - get dividerDragger() { - return this.previewWidth() === 0 ? (null) : -
    -
    -
    ; - } - - @computed - get previewPanel() { - return
    - {!this.previewDocument ? (null) : - } -
    ; - } - - @computed - get schemaTable() { - return ; - } - - @computed - public get schemaToolbar() { - return
    -
    -
    - - Show Preview -
    -
    -
    ; - } - - onSpecificMenu = (e: React.MouseEvent) => { - if ((e.target as any)?.className?.includes?.("collectionSchemaView-cell") || (e.target instanceof HTMLSpanElement)) { - const cm = ContextMenu.Instance; - const options = cm.findByDescription("Options..."); - const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; - optionItems.push({ description: "remove", event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: "trash" }); - !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); - cm.displayMenu(e.clientX, e.clientY); - (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this. - e.stopPropagation(); - } - } - - @action - onTableClick = (e: React.MouseEvent): void => { - if (!(e.target as any)?.className?.includes?.("collectionSchemaView-cell") && !(e.target instanceof HTMLSpanElement)) { - this.setPreviewDoc(undefined); - } else { - e.stopPropagation(); - } - this.setFocused(this.props.Document); - this.closeHeader(); - } - - onResizedChange = (newResized: Resize[], event: any) => { - const columns = this.columns; - newResized.forEach(resized => { - const index = columns.findIndex(c => c.heading === resized.id); - const column = columns[index]; - column.setWidth(resized.value); - columns[index] = column; - }); - this.columns = columns; - } - - @action - setColumns = (columns: SchemaHeaderField[]) => this.columns = columns - - @undoBatch - reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => { - const columns = [...columnsValues]; - const oldIndex = columns.indexOf(toMove); - const relIndex = columns.indexOf(relativeTo); - const newIndex = (oldIndex > relIndex && !before) ? relIndex + 1 : (oldIndex < relIndex && before) ? relIndex - 1 : relIndex; - - if (oldIndex === newIndex) return; - - columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]); - this.columns = columns; - } - - onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation(); - - render() { - TraceMobx(); - if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0); - const menuContent = this.renderMenuContent; - const menu =
    this.onZoomMenu(e)} - onPointerDown={e => this.onHeaderClick(e)} - style={{ transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}px)` }}> - { - const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height); - this._menuWidth = dim[0]; this._menuHeight = dim[1]; - })}> - {({ measureRef }) =>
    {menuContent}
    } -
    -
    ; - return
    -
    this.props.isContentActive(true) && e.stopPropagation()} - onDrop={e => this.onExternalDrop(e, {})} - ref={this.createTarget}> - {this.schemaTable} -
    - {this.dividerDragger} - {!this.previewWidth() ? (null) : this.previewPanel} - {this._headerOpen && this.props.isContentActive() ? menu : null} -
    ; - } -} \ No newline at end of file diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx index a25f962df..c659f749e 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx @@ -1,18 +1,17 @@ import React = require("react"); -import { IconProp, library } from "@fortawesome/fontawesome-svg-core"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; +import { action, computed, observable, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, Opt } from "../../../../fields/Doc"; +import { Doc, DocListCast, Opt, StrListCast } from "../../../../fields/Doc"; import { listSpec } from "../../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { ScriptField } from "../../../../fields/ScriptField"; import { Cast, StrCast } from "../../../../fields/Types"; import { undoBatch } from "../../../util/UndoManager"; -import { SearchBox } from "../../search/SearchBox"; +import { CollectionView } from "../CollectionView"; import { ColumnType } from "./CollectionSchemaView"; import "./CollectionSchemaView.scss"; -import { CollectionView } from "../CollectionView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -26,9 +25,9 @@ export interface AddColumnHeaderProps { @observer export class CollectionSchemaAddColumnHeader extends React.Component { render() { - return ( - - ); + return ; } } @@ -234,7 +233,7 @@ export interface KeysDropdownProps { @observer export class KeysDropdown extends React.Component { @observable private _key: string = this.props.keyValue; - @observable private _searchTerm: string = this.props.keyValue; + @observable private _searchTerm: string = this.props.keyValue + ":"; @observable private _isOpen: boolean = false; @observable private _node: HTMLDivElement | null = null; @observable private _inputRef: React.RefObject = React.createRef(); @@ -326,11 +325,11 @@ export class KeysDropdown extends React.Component { || ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null); return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm)); } - @action - renderOptions = (): JSX.Element[] | JSX.Element => { + + @computed get renderOptions() { if (!this._isOpen) { this.defaultMenuHeight = 0; - return <>; + return (null); } const options = this.showKeys.map(key => { return
    { return options; } - docSafe: Doc[] = []; + @computed get docSafe() { return DocListCast(this.props.dataDoc?.[this.props.fieldKey]); } - @action - renderFilterOptions = (): JSX.Element[] | JSX.Element => { + @computed get renderFilterOptions() { if (!this._isOpen || !this.props.dataDoc) { this.defaultMenuHeight = 0; - return <>; + return (null); } const keyOptions: string[] = []; const colpos = this._searchTerm.indexOf(":"); const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); - if (this.docSafe.length === 0) { - this.docSafe = DocListCast(this.props.dataDoc[this.props.fieldKey]); - } - const docs = this.docSafe; - docs.forEach((doc) => { + this.docSafe.forEach(doc => { const key = StrCast(doc[this._key]); if (keyOptions.includes(key) === false && key.includes(temp) && key !== "") { keyOptions.push(key); } }); - const filters = Cast(this.props.Document._docFilters, listSpec("string")); - if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) { + const filters = StrListCast(this.props.Document._docFilters); + if (filters.some(filter => filter.split(":")[0] === this._key) === false) { this.props.col.setColor("rgb(241, 239, 235)"); this.closeResultsVisibility = "none"; } @@ -408,9 +402,9 @@ export class KeysDropdown extends React.Component { const options = keyOptions.map(key => { let bool = false; if (filters !== undefined) { - const ind = filters.findIndex(filter => filter.split(":")[0] === key); + const ind = filters.findIndex(filter => filter.split(":")[1] === key); const fields = ind === -1 ? undefined : filters[ind].split(":"); - bool = fields ? fields[1] === "check" : false; + bool = fields ? fields[2] === "check" : false; } return
    { e.stopPropagation()} onClick={e => e.stopPropagation()} - onChange={(e) => { - e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, "remove"); - e.target.checked === true ? this.closeResultsVisibility = "contents" : console.log(""); - e.target.checked === true ? this.props.col.setColor("green") : this.updateFilter(); - e.target.checked === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, "remove"); - }} + onChange={action(e => { + if (e.target.checked) { + Doc.setDocFilter(this.props.Document, this._key, key, "check"); + this.closeResultsVisibility = "contents"; + this.props.col.setColor("green"); + } else { + Doc.setDocFilter(this.props.Document, this._key, key, "remove"); + this.updateFilter(); + } + })} checked={bool} /> @@ -472,11 +470,7 @@ export class KeysDropdown extends React.Component { removeFilters = (e: React.PointerEvent): void => { const keyOptions: string[] = []; - if (this.docSafe.length === 0 && this.props.dataDoc) { - this.docSafe = DocListCast(this.props.dataDoc[this.props.fieldKey]); - } - const docs = this.docSafe; - docs.forEach((doc) => { + this.docSafe.forEach(doc => { const key = StrCast(doc[this._key]); if (keyOptions.includes(key) === false) { keyOptions.push(key); @@ -494,10 +488,6 @@ export class KeysDropdown extends React.Component {
    - {/* { - runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen }) - }} /> */} -
    { {!this._isOpen ? (null) :
    - {this._searchTerm.includes(":") ? this.renderFilterOptions() : this.renderOptions()} + {this._searchTerm.includes(":") ? this.renderFilterOptions : this.renderOptions}
    }
    diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 12493ecc1..b89246489 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,6 +1,6 @@ import React = require("react"); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, untracked } from "mobx"; +import { action, computed, observable, untracked, trace } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import { Resize } from "react-table"; diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index d157832d9..bc5a9559f 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -1,7 +1,7 @@ import React = require("react"); import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table"; import "react-table/react-table.css"; @@ -386,13 +386,13 @@ export class SchemaTable extends React.Component { @undoBatch @action createColumn = () => { - let index = 0; - let found = this.props.columns.findIndex(col => col.heading.toUpperCase() === "New field".toUpperCase()) > -1; - while (found) { - index++; - found = this.props.columns.findIndex(col => col.heading.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1; + const newFieldName = (index: number) => `New field${index ? ` (${index})` : ""}`; + for (let index = 0; index < 100; index++) { + if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) { + this.props.columns.push(new SchemaHeaderField(newFieldName(index), "#f1efeb")); + break; + } } - this.props.columns.push(new SchemaHeaderField(`New field ${index ? "(" + index + ")" : ""}`, "#f1efeb")); } @action diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index d8690831f..9490edc2c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1142,7 +1142,7 @@ export namespace Doc { runInAction(() => { for (let i = 0; i < docFilters.length; i++) { const fields = docFilters[i].split(":"); // split key:value:modifier - if (fields[0] === key && (fields[1] === value || modifiers === "match" || modifiers === "remove")) { + if (fields[0] === key && (fields[1] === value || modifiers === "match")) { if (fields[2] === modifiers && modifiers && fields[1] === value) { if (toggle) modifiers = "remove"; else return; -- cgit v1.2.3-70-g09d2 From 1c50b9607d6ea6916e6ae32d9d51fba096421b08 Mon Sep 17 00:00:00 2001 From: geireann Date: Wed, 6 Oct 2021 18:09:54 -0400 Subject: presBox type fix --- src/client/views/nodes/trails/PresBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 8e61a224c..8a3df09e3 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1143,10 +1143,10 @@ export class PresBox extends ViewBoxBaseComponent @computed get transitionDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const type = targetDoc.type; const isPresCollection: boolean = (targetDoc === this.layoutDoc.presCollection); const isPinWithView: boolean = BoolCast(activeItem.presPinView); if (activeItem && targetDoc) { + const type = targetDoc.type; const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); -- cgit v1.2.3-70-g09d2 From a46d659229120cdb139f716b1a48ec6b887807bb Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 6 Oct 2021 19:40:40 -0400 Subject: fixed lightbox view to shrinkwrap collections when opened, but not force fit contents to allow users to navigate. fixed comparison box to show context of maker'/annotation documents. fixed ink pointer events to honor pointerEvents prop (eg when embedded in a comparisonBox that turns them off). --- src/client/views/InkingStroke.tsx | 2 +- src/client/views/LightboxView.tsx | 4 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionSchema/CollectionSchemaCells.tsx | 1 - src/client/views/nodes/ComparisonBox.tsx | 12 +++++++++--- 5 files changed, 13 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index d05a4a6e4..752db1413 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -209,7 +209,7 @@ export class InkingStroke extends ViewBoxBaseComponent { LightboxView.SetLightboxDoc(undefined); } }} > - +
    { DataDoc={undefined} LayoutTemplate={LightboxView.LightboxDocTemplate} addDocument={undefined} - fitContentsToDoc={this.fitToBox} + // fitContentsToDoc={this.fitToBox} // bcz: why do we want this? when we initially open a colletion, we shrinkwrap it which allows for user navigation. if we later encounter a collection, it's not clear to me that we want to make it either shrinkwrap or fitContents... isDocumentActive={returnFalse} isContentActive={returnTrue} addDocTab={this.addDocTab} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b2db1168d..7dcd63b80 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1036,7 +1036,7 @@ export class CollectionFreeFormView extends CollectionSubView; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index ed196349e..0274cc49c 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -103,7 +103,6 @@ export class CollectionSchemaCell extends React.Component { this.props.changeFocusedCellByIndex(this.props.row, this.props.col); this.props.setPreviewDoc(this.props.rowProps.original); - console.log("click cell"); let url: string; if (url = StrCast(this.props.rowProps.row.href)) { try { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index f23c68409..b1aada158 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -89,14 +89,20 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { var whichDoc = Cast(this.dataDoc[which], Doc, null); - if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null); + //if (whichDoc?.type === DocumentType.MARKER) + const targetDoc = Cast(whichDoc.annotationOn, Doc, null) ?? whichDoc; return whichDoc ? <> - { + whichDoc !== targetDoc && r?.focus(targetDoc); + }} + {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} isContentActive={returnFalse} isDocumentActive={returnFalse} styleProvider={this.docStyleProvider} - Document={whichDoc} + Document={targetDoc} DataDoc={undefined} + hideLinkButton={true} pointerEvents={"none"} /> {clearButton(which)} : // placeholder image if doc is missing -- cgit v1.2.3-70-g09d2 From 8691e25e2f65e5041e147d37374c77947298a4b5 Mon Sep 17 00:00:00 2001 From: geireann Date: Wed, 6 Oct 2021 20:02:24 -0400 Subject: added zoom --- src/client/util/DocumentManager.ts | 5 +++-- src/client/views/nodes/trails/PresBox.tsx | 37 ++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index b66befb08..66b6a1e44 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -161,7 +161,8 @@ export class DocumentManager { originatingDoc: Opt = undefined, // doc that initiated the display of the target odoc finished?: () => void, originalTarget?: Doc, - noSelect?: boolean + noSelect?: boolean, + presZoom?: number ): Promise => { originalTarget = originalTarget ?? targetDoc; const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; @@ -194,7 +195,7 @@ export class DocumentManager { if (focusView) { !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox focusView.focus(targetDoc, { - originalTarget, willZoom, afterFocus: (didFocus: boolean) => + originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(didFocus); res(); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 8a3df09e3..2a153f256 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -404,7 +404,7 @@ export class PresBox extends ViewBoxBaseComponent } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { LightboxView.SetLightboxDoc(undefined); //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right + await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. @@ -1026,6 +1026,15 @@ export class PresBox extends ViewBoxBaseComponent Array.from(this._selectedArray.keys()).forEach((doc) => doc.presTransition = timeInMS); } + // Converts seconds to ms and updates presTransition + setZoom = (number: String, change?: number) => { + let scale = Number(number) / 100; + if (change) scale += change; + if (scale < 0.01) scale = 0.01; + if (scale > 1.5) scale = 1.5; + Array.from(this._selectedArray.keys()).forEach((doc) => doc.presZoom = scale); + } + // Converts seconds to ms and updates presDuration setDurationTime = (number: String, change?: number) => { let timeInMS = Number(number) * 1000; @@ -1148,6 +1157,7 @@ export class PresBox extends ViewBoxBaseComponent if (activeItem && targetDoc) { const type = targetDoc.type; const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; + const zoom = activeItem.presZoom ? NumCast(activeItem.presZoom) * 100 : 75; let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None'; @@ -1172,6 +1182,31 @@ export class PresBox extends ViewBoxBaseComponent
    } +
    +
    Zoom (% screen filled)
    +
    + this.setZoom(e.target.value))} />% +
    +
    +
    this.setZoom(String(zoom), 0.1))}> + +
    +
    this.setZoom(String(zoom), -0.1))}> + +
    +
    +
    + this._batch = UndoManager.StartBatch("presZoom")} + onPointerUp={() => this._batch?.end()} + onChange={(e: React.ChangeEvent) => { + e.stopPropagation(); + this.setZoom(e.target.value); + }} />
    Movement Speed
    -- cgit v1.2.3-70-g09d2 From 43496b4cd19a06d2682f2416a4273e25cdcb55a0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 7 Oct 2021 08:47:22 -0400 Subject: Revert "Revert "Merge pull request #34 from brown-dash/linking-anh"" This reverts commit 4e6a1d7a37c8c28014a9f7cd0d92f17c8f29454d. --- src/client/documents/Documents.ts | 1 + src/client/util/LinkManager.ts | 2 ++ .../CollectionFreeFormLinkView.tsx | 15 ++++++++++++--- src/client/views/linking/LinkEditor.tsx | 20 ++++++++++++++++++-- src/client/views/linking/LinkMenuItem.tsx | 4 ++-- src/fields/Doc.ts | 1 + 6 files changed, 36 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e3623c069..6d64b3286 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -302,6 +302,7 @@ export class DocumentOptions { border?: string; //for searchbox hoverBackgroundColor?: string; // background color of a label when hovered linkRelationshipList?: List; // for storing different link relationships (when set by user in the link editor) + linkRelationshipSizes?: List; //stores number of links contained in each relationship linkColorList?: List; // colors of links corresponding to specific link relationships } export namespace Docs { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 53bd13fb3..3f7f16113 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -107,8 +107,10 @@ export class LinkManager { if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) { const linkRelationshipList = new List(); const linkColorList = new List(); + const linkRelationshipSizes = new List(); Doc.UserDoc().linkRelationshipList = linkRelationshipList; Doc.UserDoc().linkColorList = linkColorList; + Doc.UserDoc().linkRelationshipSizes = linkRelationshipSizes; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index f6c2707da..88cd0feb3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -42,7 +42,7 @@ export class CollectionFreeFormLinkView extends React.Component this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() + setTimeout(action(() => this._opacity = 0.75), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); @@ -180,15 +180,24 @@ export class CollectionFreeFormLinkView extends React.Component; const linkColorList = Doc.UserDoc().linkColorList as List; + const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List; + const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); + //access stroke color using index of the relationship in the color list (default black) - const strokeColor = linkRelationshipList.indexOf(linkRelationship) === -1 ? (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "white" : "black") : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; + const strokeColor = currRelationshipIndex == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; + + //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) + //thickness varies linearly from 3px to 12px for increasing link count + const strokeWidth: string = currRelationshipIndex == -1 ? "3px" : Math.floor(2 + 10 * (linkRelationshipSizes[currRelationshipIndex] / Math.max(...linkRelationshipSizes))) + "px"; + return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - {textX === undefined ? (null) : {Field.toString(this.props.LinkDocs[0].description as any as Field)} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 219f7d3a2..58c57a23b 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, StrListCast, Field } from "../../../fields/Doc"; +import { Doc, NumListCast, StrListCast, Field } from "../../../fields/Doc"; import { DateCast, StrCast, Cast } from "../../../fields/Types"; import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; @@ -42,14 +42,28 @@ export class LinkEditor extends React.Component { @undoBatch setRelationshipValue = action((value: string) => { if (LinkManager.currentLink) { + const prevRelationship = LinkManager.currentLink.linkRelationship as string; + LinkManager.currentLink.linkRelationship = value; Doc.GetProto(LinkManager.currentLink).linkRelationship = value; const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); + const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().linkColorList); + // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color if (linkRelationshipList && !linkRelationshipList.includes(value)) { linkRelationshipList.push(value); + linkRelationshipSizes.push(1); const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; - linkColorList.push(randColor); + linkColorList.push(randColor) + // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes + } else if (linkRelationshipList && value != prevRelationship) { + //increment size of new relationship size + linkRelationshipSizes[linkRelationshipList.indexOf(value)] = linkRelationshipSizes[linkRelationshipList.indexOf(value)] + 1; + //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) + if (linkRelationshipList.includes(prevRelationship)) { + linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] = linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] - 1; + } + } this.relationshipButtonColor = "rgb(62, 133, 55)"; setTimeout(action(() => this.relationshipButtonColor = ""), 750); @@ -141,6 +155,7 @@ export class LinkEditor extends React.Component { style={{ width: "100%" }} id="input" value={this.relationship} + autoComplete={"off"} placeholder={"Enter link relationship"} onKeyDown={this.onRelationshipKey} onChange={this.handleRelationshipChange} @@ -169,6 +184,7 @@ export class LinkEditor extends React.Component {
    {
    {this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}
    }>
    e.stopPropagation()}> -
    +
    {!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}
    }>
    e.stopPropagation()}> -
    +
    {!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}
    }> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 9490edc2c..a89bdf57a 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -79,6 +79,7 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { export async function DocCastAsync(field: FieldResult): Promise> { return Cast(field, Doc); } +export function NumListCast(field: FieldResult) { return Cast(field, listSpec("number"), []); } export function StrListCast(field: FieldResult) { return Cast(field, listSpec("string"), []); } export function DocListCast(field: FieldResult) { return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; } export function DocListCastOrNull(field: FieldResult) { return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; } -- cgit v1.2.3-70-g09d2 From 8f1a6266cf0a728ab2b7df11d45a023de266fae4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 7 Oct 2021 09:43:19 -0400 Subject: link menu ui tweaking. edge case checks for linkrelationship stuff. default links to have linkAutoMove --- src/Utils.ts | 2 +- src/client/documents/Documents.ts | 2 ++ src/client/util/LinkManager.ts | 11 +++-------- .../collectionFreeForm/CollectionFreeFormLinkView.tsx | 8 +++++--- src/client/views/linking/LinkEditor.tsx | 14 +++++++++++--- src/client/views/linking/LinkMenuGroup.tsx | 1 - src/client/views/linking/LinkMenuItem.tsx | 18 +++++++++--------- 7 files changed, 31 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 7ec4f69f3..53182cc9c 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -575,7 +575,7 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe export function DashColor(color: string) { try { - return Color(color.toLowerCase()); + return color ? Color(color.toLowerCase()) : Color("transparent"); } catch (e) { console.log("COLOR error:", e); return Color("red"); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6d64b3286..da5c8efa9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -191,6 +191,7 @@ export class DocumentOptions { opacity?: number; defaultBackgroundColor?: string; _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked + _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint isFolder?: boolean; 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 @@ -1106,6 +1107,7 @@ export namespace DocUtils { "_acl-Public": SharingPermissions.Augment, linkDisplay: true, _hidden: true, + _linkAutoMove: true, linkRelationship, _showCaption: "description", _showTitle: "linkRelationship", diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 3f7f16113..90a8f2737 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -104,14 +104,9 @@ export class LinkManager { public createLinkrelationshipLists = () => { //create new lists for link relations and their associated colors if the lists don't already exist - if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) { - const linkRelationshipList = new List(); - const linkColorList = new List(); - const linkRelationshipSizes = new List(); - Doc.UserDoc().linkRelationshipList = linkRelationshipList; - Doc.UserDoc().linkColorList = linkColorList; - Doc.UserDoc().linkRelationshipSizes = linkRelationshipSizes; - } + !Doc.UserDoc().linkRelationshipList && (Doc.UserDoc().linkRelationshipList = new List()); + !Doc.UserDoc().linkColorList && (Doc.UserDoc().linkColorList = new List()); + !Doc.UserDoc().linkRelationshipSizes && (Doc.UserDoc().linkRelationshipSizes = new List()); } public addLink(linkDoc: Doc, checkExists = false) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 88cd0feb3..bb4cae8c6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -189,15 +189,17 @@ export class CollectionFreeFormLinkView extends React.Component; const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); + const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex]; + //access stroke color using index of the relationship in the color list (default black) - const strokeColor = currRelationshipIndex == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; + const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? "black" : linkColorList[currRelationshipIndex]; //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) //thickness varies linearly from 3px to 12px for increasing link count - const strokeWidth: string = currRelationshipIndex == -1 ? "3px" : Math.floor(2 + 10 * (linkRelationshipSizes[currRelationshipIndex] / Math.max(...linkRelationshipSizes))) + "px"; + const strokeWidth = linkSize === -1 ? "3px" : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + "px"; return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - {textX === undefined ? (null) : {Field.toString(this.props.LinkDocs[0].description as any as Field)} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 58c57a23b..5b5c3cd01 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -50,18 +50,26 @@ export class LinkEditor extends React.Component { const linkColorList = StrListCast(Doc.UserDoc().linkColorList); // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color - if (linkRelationshipList && !linkRelationshipList.includes(value)) { + if (!linkRelationshipList?.includes(value)) { linkRelationshipList.push(value); linkRelationshipSizes.push(1); const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; linkColorList.push(randColor) // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes } else if (linkRelationshipList && value != prevRelationship) { + const index = linkRelationshipList.indexOf(value); //increment size of new relationship size - linkRelationshipSizes[linkRelationshipList.indexOf(value)] = linkRelationshipSizes[linkRelationshipList.indexOf(value)] + 1; + if (index !== -1 && index < linkRelationshipSizes.length) { + const pvalue = linkRelationshipSizes[index]; + linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1); + } //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) if (linkRelationshipList.includes(prevRelationship)) { - linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] = linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] - 1; + const pindex = linkRelationshipList.indexOf(prevRelationship); + if (pindex !== -1 && pindex < linkRelationshipSizes.length) { + const pvalue = linkRelationshipSizes[pindex]; + linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1)); + } } } diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index cb6571f92..03377ad4e 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -31,7 +31,6 @@ export class LinkMenuGroup extends React.Component { if (RGBcolor) { //set opacity to 0.25 by modifiying the rgb string color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)"; - console.log(color); } } return color; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index e0b20ef6f..f19064e49 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -165,19 +165,19 @@ export class LinkMenuItem extends React.Component {
    -
    {this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}
    }> -
    e.stopPropagation()}> -
    +
    {this.props.linkDoc.hidden ? "Show Link Anchor" : "Hide Link Anchor"}
    }> +
    e.stopPropagation()}> +
    -
    {!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}
    }> -
    e.stopPropagation()}> -
    +
    {this.props.linkDoc.linkDisplay ? "Hide Link Line" : "Show Link Line"}
    }> +
    e.stopPropagation()}> +
    -
    {!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}
    }> -
    e.stopPropagation()}> -
    +
    {this.props.linkDoc.linkAutoMove ? "Click to freeze link anchor position" : "Click to auto move link anchor"}
    }> +
    e.stopPropagation()}> +
    Edit Link
    }> -- cgit v1.2.3-70-g09d2 From 662176f25e25d3bf31cfb8ec6e3792d18f77f37d Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 7 Oct 2021 09:52:23 -0400 Subject: from linking-anh merge --- src/client/views/linking/LinkMenuItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index f19064e49..96cc6d600 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -176,7 +176,7 @@ export class LinkMenuItem extends React.Component {
    {this.props.linkDoc.linkAutoMove ? "Click to freeze link anchor position" : "Click to auto move link anchor"}
    }> -
    e.stopPropagation()}> +
    e.stopPropagation()}>
    -- cgit v1.2.3-70-g09d2 From ed68bbec549dedeb89bcb584151b097863b52d0d Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 14 Oct 2021 12:16:17 -0400 Subject: fixed comparison box error. --- src/client/views/nodes/ComparisonBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index b1aada158..0f962f95c 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -90,7 +90,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { var whichDoc = Cast(this.dataDoc[which], Doc, null); //if (whichDoc?.type === DocumentType.MARKER) - const targetDoc = Cast(whichDoc.annotationOn, Doc, null) ?? whichDoc; + const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc; return whichDoc ? <> { -- cgit v1.2.3-70-g09d2 From 93bc6d3fbfa35c7f47b7000e0ea36d9ab81d7eba Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 14 Oct 2021 15:33:41 -0400 Subject: fixed document resizing. --- src/client/views/DocumentDecorations.tsx | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index bd9c3509b..17a81149c 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -318,8 +318,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P const doc = Document(docView.rootDoc); const nwidth = docView.nativeWidth; const nheight = docView.nativeHeight; - const width = (doc._width || 0); - let height = (doc._height || (nheight / nwidth * width)); + const docheight = doc._height || 0; + const docwidth = doc._width || 0; + const width = docwidth; + let height = (docheight || (nheight / nwidth * width)); height = !height || isNaN(height) ? 20 : height; const scale = docView.props.ScreenToLocalTransform().Scale; const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable; @@ -332,17 +334,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P else dW = dH * nwidth / nheight; } } - const actualdW = Math.max(width + (dW * scale), 20); - const actualdH = Math.max(height + (dH * scale), 20); - doc.x = (doc.x || 0) + dX * (actualdW - width); - doc.y = (doc.y || 0) + dY * (actualdH - height); + let actualdW = Math.max(width + (dW * scale), 20); + let actualdH = Math.max(height + (dH * scale), 20); const fixedAspect = (nwidth && nheight); if (fixedAspect) { if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) { if (dragRight && modifyNativeDim) { doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc); } else { - if (!doc._fitWidth) doc._height = nheight / nwidth * actualdW; + if (!doc._fitWidth) { + actualdH = nheight / nwidth * actualdW; + doc._height = actualdH; + } else if (!modifyNativeDim || dragBotRight) doc._height = actualdH; } doc._width = actualdW; @@ -353,10 +356,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc); doc._autoHeight = false; } else { - if (!doc._fitWidth) doc._width = nwidth / nheight * actualdH; + if (!doc._fitWidth) { + actualdW = nwidth / nheight * actualdH; + doc._width = actualdW; + } else if (!modifyNativeDim || dragBotRight) doc._width = actualdW; } - if (!modifyNativeDim) doc._height = Math.min(nheight / nwidth * NumCast(doc._width), actualdH); + if (!modifyNativeDim) { + actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH); + doc._height = actualdH; + } else doc._height = actualdH; } } else { @@ -364,6 +373,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P dW && (doc._width = actualdW); dH && (doc._autoHeight = false); } + doc.x = (doc.x || 0) + dX * (actualdW - docwidth); + doc.y = (doc.y || 0) + dY * (actualdH - docheight); doc._lastModified = new DateField(); } const val = this._dragHeights.get(docView.layoutDoc); -- cgit v1.2.3-70-g09d2 From b08232def0a7533cc7dbc7db8d3153e72a3ff2d5 Mon Sep 17 00:00:00 2001 From: Aubrey Li Date: Thu, 14 Oct 2021 15:50:54 -0400 Subject: dashboard treeview freeze --- src/client/util/CurrentUserUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 3c32c2359..9d06ad8a3 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -810,7 +810,7 @@ export class CurrentUserUtils { const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); const newDashboardButton: Doc = Docs.Create.FontIconDocument({ onClick: newDashboard, _forceActive: true, toolTip: "Create new dashboard", _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true }); doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, + title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true, @@ -1328,6 +1328,7 @@ export class CurrentUserUtils { Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); doc.savedFilters = new List(); doc.filterDocCount = 0; + doc.freezeChildren = "remove|add"; this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates this.setupImportSidebar(doc); // sets up the import sidebar -- cgit v1.2.3-70-g09d2 From bdf0befa2b5eff79c2729254c2d053afe18b1646 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 16 Oct 2021 01:58:15 -0400 Subject: fixed warnings/errors & redirection to /home --- src/client/views/collections/TreeView.tsx | 12 ++++++------ .../collections/collectionSchema/CollectionSchemaHeaders.tsx | 4 ++-- src/client/views/linking/LinkEditor.tsx | 4 ++-- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/server/server_Initialization.ts | 2 ++ 5 files changed, 13 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index a3da0e0e4..7f2128230 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -820,7 +820,7 @@ export class TreeView extends React.Component { childDocs: Doc[], treeView: CollectionTreeView, parentTreeView: CollectionTreeView | TreeView | undefined, - conainerCollection: Doc, + containerCollection: Doc, dataDoc: Doc | undefined, parentCollectionDoc: Doc | undefined, containerPrevSibling: Doc | undefined, @@ -846,16 +846,16 @@ export class TreeView extends React.Component { unobserveHeight: (ref: any) => void, contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[]) ) { - const viewSpecScript = Cast(conainerCollection.viewSpecScript, ScriptField); + const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField); if (viewSpecScript) { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } - const docs = TreeView.sortDocs(childDocs, StrCast(conainerCollection.treeViewSortCriterion)); + const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion)); const rowWidth = () => panelWidth() - treeBulletWidth(); const treeViewRefs = new Map(); return docs.filter(child => child instanceof Doc).map((child, i) => { - const pair = Doc.GetLayoutDataDocPair(conainerCollection, dataDoc, child); + const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return (null); } @@ -883,7 +883,7 @@ export class TreeView extends React.Component { return treeViewRefs.set(child, r ? r : undefined)} document={pair.layout} dataDoc={pair.data} - containerCollection={conainerCollection} + containerCollection={containerCollection} prevSibling={docs[i]} treeView={treeView} indentDocument={indent} @@ -891,7 +891,7 @@ export class TreeView extends React.Component { onCheckedClick={onCheckedClick} onChildClick={onChildClick} renderDepth={renderDepth} - removeDoc={StrCast(conainerCollection.freezeChildren).includes("remove") ? undefined : remove} + removeDoc={StrCast(containerCollection.freezeChildren).includes("remove") ? undefined : remove} addDocument={addDocument} styleProvider={styleProvider} panelWidth={rowWidth} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx index 074074bc5..1306b79cb 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx @@ -395,8 +395,8 @@ export class KeysDropdown extends React.Component { this.closeResultsVisibility = "none"; } for (let i = 0; i < (filters?.length ?? 0) - 1; i++) { - if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i].split(":")[1]) === false) { - keyOptions.push(filters![i + 1]); + if (filters[i] === this.props.col.heading && keyOptions.includes(filters[i].split(":")[1]) === false) { + keyOptions.push(filters[i + 1]); } } const options = keyOptions.map(key => { diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 5b5c3cd01..db331bb75 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -54,9 +54,9 @@ export class LinkEditor extends React.Component { linkRelationshipList.push(value); linkRelationshipSizes.push(1); const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; - linkColorList.push(randColor) + linkColorList.push(randColor); // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes - } else if (linkRelationshipList && value != prevRelationship) { + } else if (linkRelationshipList && value !== prevRelationship) { const index = linkRelationshipList.indexOf(value); //increment size of new relationship size if (index !== -1 && index < linkRelationshipSizes.length) { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 0f962f95c..750213e67 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -88,7 +88,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent; }; const displayDoc = (which: string) => { - var whichDoc = Cast(this.dataDoc[which], Doc, null); + const whichDoc = Cast(this.dataDoc[which], Doc, null); //if (whichDoc?.type === DocumentType.MARKER) const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc; return whichDoc ? <> diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index 0f4a067fc..00a801e03 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -37,6 +37,8 @@ export let resolvedServerUrl: string; export default async function InitializeServer(routeSetter: RouteSetter) { const app = buildWithMiddleware(express()); + // Root route of express app + app.get("/", (req, res) => res.redirect("/home")); app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader("Access-Control-Allow-Origin", "*") -- cgit v1.2.3-70-g09d2 From 3e0a9ff2c708891a15a681e5af549caf0b18ff60 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 26 Oct 2021 12:06:36 -0400 Subject: when document is in lightbox view and a link is followed to its sidebar, we no longer reopen the document in its context. fixed selecting annotations on web/pdf. don't change document height when shown in a linkPreview. webBox fixes for pages with scripts. fixed range bounds when clicking on web text several fixes to web pages on server and client. client webbox allows clicks on divs with onclick instead of doing selection also hacky fix so that google search url doesn't keep expanding by removing 'q=' regions also added prevent/allow script menu item server grabs all html and hides id="google.." which are ads. also rewrites hrefs starting with http to route through corsProxy also removes target=_blank tags to prevent pages from opening outside of dash. also cleaned up routes and comments also when not logged in, references to anything in dash domain route to /home --- package-lock.json | 5 + package.json | 1 + src/Utils.ts | 2 +- src/client/util/LinkManager.ts | 6 +- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/InkControlPtHandles.tsx | 16 +-- src/client/views/InkStrokeProperties.ts | 72 +++++++------ src/client/views/InkTangentHandles.tsx | 6 +- src/client/views/InkingStroke.tsx | 9 +- src/client/views/collections/CollectionSubView.tsx | 5 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 77 +++++++++++--- src/server/server_Initialization.ts | 115 ++++++++++++--------- 14 files changed, 206 insertions(+), 122 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index c1dd8506f..3aff3a549 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9264,6 +9264,11 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "optional": true }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" + }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", diff --git a/package.json b/package.json index 5d10c0d54..99c818062 100644 --- a/package.json +++ b/package.json @@ -194,6 +194,7 @@ "libxmljs": "^0.19.7", "lodash": "^4.17.15", "material-ui": "^0.20.2", + "memorystream": "^0.3.1", "mobile-detect": "^1.4.4", "mobx": "^5.15.7", "mobx-react": "^5.4.4", diff --git a/src/Utils.ts b/src/Utils.ts index 53182cc9c..bfb29fe8d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -599,7 +599,7 @@ export function getWordAtPoint(elem: any, x: number, y: number): string | undefi range.selectNodeContents(elem); var currentPos = 0; const endPos = range.endOffset; - while (currentPos + 1 < endPos) { + while (currentPos + 1 <= endPos) { range.setStart(elem, currentPos); range.setEnd(elem, currentPos + 1); const rangeRect = range.getBoundingClientRect(); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 90a8f2737..62b13e2c6 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -234,8 +234,10 @@ export class LinkManager { setTimeout(LightboxView.Next); finished?.(); } else { - const containerDoc = Cast(target.annotationOn, Doc, null) || target; - const targetContext = Cast(containerDoc?.context, Doc, null); + const containerAnnoDoc = Cast(target.annotationOn, Doc, null); + const containerDoc = containerAnnoDoc || target; + const containerDocContext = Cast(containerDoc?.context, Doc, null); + const targetContext = LightboxView.LightboxDoc ? containerAnnoDoc || containerDocContext : containerDocContext; const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 17a81149c..5b44a0552 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -195,11 +195,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P @action onRotateDown = (e: React.PointerEvent): void => { this._rotateUndo = UndoManager.StartBatch("rotatedown"); + const pt = { x: (this.Bounds.x + this.Bounds.r) / 2, y: (this.Bounds.y + this.Bounds.b) / 2 }; setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { const movement = { X: delta[0], Y: e.clientY - down[1] }; const angle = Math.max(1, Math.abs(movement.Y / 10)); - InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180)); + const selectedInk = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK); + InkStrokeProperties.Instance?.rotateInk(selectedInk, 2 * movement.X / angle * (Math.PI / 180), pt); return false; }, () => { diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 0644488b3..73de4a3e0 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -3,18 +3,20 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; import { ControlPoint, InkData, PointData } from "../../fields/InkField"; +import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast } from "../../fields/Types"; import { setupMoveUpEvents } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; -import { InkStrokeProperties } from "./InkStrokeProperties"; -import { List } from "../../fields/List"; import { InkingStroke } from "./InkingStroke"; +import { InkStrokeProperties } from "./InkStrokeProperties"; +import { DocumentView } from "./nodes/DocumentView"; export interface InkControlProps { inkDoc: Doc; + inkView: DocumentView; inkCtrlPoints: InkData; screenCtrlPoints: InkData; screenSpaceLineWidth: number; @@ -51,12 +53,12 @@ export class InkControlPtHandles extends React.Component { setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); - InkStrokeProperties.Instance?.moveControlPtHandle(delta[0] * screenScale, delta[1] * screenScale, controlIndex); + InkStrokeProperties.Instance?.moveControlPtHandle(this.props.inkView, delta[0] * screenScale, delta[1] * screenScale, controlIndex); return false; }), action(() => { if (this.controlUndo) { - InkStrokeProperties.Instance?.snapControl(this.props.inkDoc, controlIndex); + InkStrokeProperties.Instance?.snapControl(this.props.inkView, controlIndex); } this.controlUndo?.end(); this.controlUndo = undefined; @@ -71,11 +73,11 @@ export class InkControlPtHandles extends React.Component { } else { if (brokenIndices?.includes(equivIndex)) { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB); + InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB); } if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB); + InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB); } } this.controlUndo?.end(); @@ -98,7 +100,7 @@ export class InkControlPtHandles extends React.Component { @action onDelete = (e: KeyboardEvent) => { if (["-", "Backspace", "Delete"].includes(e.key)) { - InkStrokeProperties.Instance?.deletePoints(); + InkStrokeProperties.Instance?.deletePoints(this.props.inkView); e.stopPropagation(); } } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index ee30caa3d..33e25bbbb 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,16 +1,15 @@ import { Bezier } from "bezier-js"; -import { action, computed, observable, reaction } from "mobx"; -import { Doc } from "../../fields/Doc"; -import { Document } from "../../fields/documentSchemas"; +import { action, observable, reaction } from "mobx"; +import { Doc, Opt } from "../../fields/Doc"; import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast, NumCast } from "../../fields/Types"; import { DocumentType } from "../documents/DocumentTypes"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; import { InkingStroke } from "./InkingStroke"; +import { DocumentView } from "./nodes/DocumentView"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; @@ -25,21 +24,16 @@ export class InkStrokeProperties { reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false)); } - @computed get selectedInk() { - const inks = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK); - return inks.length ? inks : undefined; - } - /** * Helper function that enables other functions to be applied to a particular ink instance. * @param func The inputted function. * @param requireCurrPoint Indicates whether the current selected point is needed. */ - applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => { + applyFunction = (strokes: Opt, func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => { var appliedFunc = false; - this.selectedInk?.forEach(action(inkView => { - if (this.selectedInk?.length === 1 && (!requireCurrPoint || this._currentPoint !== -1)) { - const doc = Document(inkView.rootDoc); + (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(action(inkView => { + if (!requireCurrPoint || this._currentPoint !== -1) { + const doc = inkView.rootDoc; if (doc.type === DocumentType.INK && doc.width && doc.height) { const ink = Cast(doc.data, InkField)?.inkData; if (ink) { @@ -47,7 +41,7 @@ export class InkStrokeProperties { const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); const ptsXscale = ((NumCast(doc._width) - NumCast(doc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; const ptsYscale = ((NumCast(doc._height) - NumCast(doc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; - const newPoints = func(doc, ink, ptsXscale, ptsYscale); + const newPoints = func(inkView, ink, ptsXscale, ptsYscale, NumCast(doc.strokeWidth)); if (newPoints) { const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X)); const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y)); @@ -73,8 +67,9 @@ export class InkStrokeProperties { */ @undoBatch @action - addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => { - this.applyFunction((doc: Doc, ink: InkData) => { + addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number, Y: number }[]) => { + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const doc = view.rootDoc; const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]]; const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t); const splicepts = [...newsegs.left.points, ...newsegs.right.points]; @@ -143,9 +138,10 @@ export class InkStrokeProperties { */ @undoBatch @action - deletePoints = () => this.applyFunction((doc: Doc, ink: InkData) => { + deletePoints = (inkView: DocumentView) => this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const doc = view.rootDoc; const newPoints: { X: number, Y: number }[] = []; - const toRemove = Math.floor(((this._currentPoint + 2) / 4)); + const toRemove = Math.floor((this._currentPoint + 2) / 4); const last = this._currentPoint === ink.length - 1; for (let i = 0; i < ink.length; i++) { if (Math.floor((i + 2) / 4) !== toRemove && (toRemove !== 0 || i > 3)) { @@ -164,17 +160,22 @@ export class InkStrokeProperties { */ @undoBatch @action - rotateInk = (angle: number) => { - this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { - const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); - const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 }; + rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: { x: number, y: number }) => { + this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => { + const oldXrangeMin = Math.min(...ink.map(p => p.X)); + const oldYrangeMin = Math.min(...ink.map(p => p.Y)); + const docViewCenterPt = view.screenToLocalTransform().transformPoint(scrpt.x, scrpt.y); + const inkCenterPt = { + X: (docViewCenterPt[0] - inkStrokeWidth / 2) / xScale + oldXrangeMin, + Y: (docViewCenterPt[1] - inkStrokeWidth / 2) / yScale + oldYrangeMin + }; const newPoints = ink.map(i => { - const pt = { X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y }; + const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y }; const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale; const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y; - return { X: newX + centerPoint.X, Y: newY + centerPoint.Y }; + return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; }); + const doc = view.rootDoc; doc.rotation = NumCast(doc.rotation) + angle; return newPoints; }); @@ -185,8 +186,8 @@ export class InkStrokeProperties { */ @undoBatch @action - moveControlPtHandle = (deltaX: number, deltaY: number, controlIndex: number) => - this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { + moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number) => + this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => { const order = controlIndex % 4; const closed = InkingStroke.IsClosed(ink); @@ -235,7 +236,8 @@ export class InkStrokeProperties { /** * Handles the movement/scaling of a control point. */ - snapControl = (inkDoc: Doc, controlIndex: number) => { + snapControl = (inkView: DocumentView, controlIndex: number) => { + const inkDoc = inkView.rootDoc; const ink = Cast(inkDoc.data, InkField)?.inkData; if (ink) { const closed = InkingStroke.IsClosed(ink); @@ -257,8 +259,8 @@ export class InkStrokeProperties { const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale + (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); - if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) { - return this.moveControlPtHandle((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); + if (near / (inkView.props.ScreenToLocalTransform().Scale || 1) < 10) { + return this.moveControlPtHandle(inkView, (nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); } } return false; @@ -269,8 +271,9 @@ export class InkStrokeProperties { * @param handleIndexA The handle point that retains its current position. * @param handleIndexB The handle point that is rotated to be 180 degrees from its opposite. */ - snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => { - this.applyFunction((doc: Doc, ink: InkData) => { + snapHandleTangent = (inkView: DocumentView, controlIndex: number, handleIndexA: number, handleIndexB: number) => { + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const doc = view.rootDoc; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []); const ind = brokenIndices.findIndex(value => value === controlIndex); if (ind !== -1) { @@ -330,8 +333,9 @@ export class InkStrokeProperties { */ @undoBatch @action - moveTangentHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => - this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { + moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => + this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => { + const doc = view.rootDoc; const closed = InkingStroke.IsClosed(ink); const oldHandlePoint = ink[handleIndex]; const oppositeHandlePoint = ink[oppositeHandleIndex]; diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index df5bebf31..f88a20448 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -10,11 +10,13 @@ import { emptyFunction, setupMoveUpEvents } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; -import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkingStroke } from "./InkingStroke"; +import { InkStrokeProperties } from "./InkStrokeProperties"; +import { DocumentView } from "./nodes/DocumentView"; export interface InkHandlesProps { inkDoc: Doc; + inkView: DocumentView; screenCtrlPoints: InkData; screenSpaceLineWidth: number; ScreenToLocalTransform: () => Transform; @@ -37,7 +39,7 @@ export class InkTangentHandles extends React.Component { setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent"); if (e.altKey) this.onBreakTangent(controlIndex); - InkStrokeProperties.Instance?.moveTangentHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); + InkStrokeProperties.Instance?.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); return false; }, () => { controlUndo?.end(); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 752db1413..d312331d0 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,17 +1,17 @@ import React = require("react"); -import { Bezier } from "bezier-js"; import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; import { documentSchema } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool } from "../../fields/InkField"; import { makeInterface } from "../../fields/Schema"; -import { Cast, NumCast, StrCast, BoolCast } from "../../fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { InteractionUtils } from "../util/InteractionUtils"; +import { SnappingManager } from "../util/SnappingManager"; import { ContextMenu } from "./ContextMenu"; import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; @@ -20,7 +20,6 @@ import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; -import { SnappingManager } from "../util/SnappingManager"; import Color = require("color"); type InkDocument = makeInterface<[typeof documentSchema]>; @@ -83,7 +82,7 @@ export class InkingStroke extends ViewBoxBaseComponent (schemaCtor: (doc: Doc) => T, moreProps?: // } } if (uriList) { - console.log("Web URI = ", uriList); // const existingWebDoc = await Hypothesis.findWebDoc(uriList); // if (existingWebDoc) { // const alias = Doc.MakeAlias(existingWebDoc); @@ -390,7 +389,6 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: // addDocument(alias); // } else { - console.log("Adding ..."); const newDoc = Docs.Create.WebDocument(uriList.split("#annotations:")[0], {// clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig) ...options, title: uriList.split("#annotations:")[0], @@ -399,8 +397,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: _nativeWidth: 850, useCors: true }); - console.log(" ... " + newDoc.title); - console.log(" ... " + addDocument(newDoc) + " " + newDoc.title); + addDocument(newDoc); } return; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7dcd63b80..febccbfcc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -75,7 +75,7 @@ export type collectionFreeformViewProps = { scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; - dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are trnasparent or not. + dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. }; @@ -1472,7 +1472,7 @@ export class CollectionFreeFormView extends CollectionSubView -
    +
    {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)} this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; - setHeight = (height: number) => this.layoutDoc._height = height; + setHeight = (height: number) => { + if (this.props.renderDepth !== -1) { + this.layoutDoc._height = height; + } + } setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view); isContentActive = (outsideReaction?: boolean) => { return CurrentUserUtils.SelectedTool !== InkTool.None || diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 37d716993..9956cc36b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -58,8 +58,10 @@ export class WebBox extends ViewBoxAnnotatableComponent(); private _searchRef = React.createRef(); private _searchString = ""; + @observable private _webUrl = ""; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't wan the src parameter to also change as that would cause an unnecessary re-render. + @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe. so thisforces the ifrmae to be rebuilt when allowScripts is toggled @observable private _searching: boolean = false; - @observable _showSidebar = false; + @observable private _showSidebar = false; @observable private _scrollTimer: any; @observable private _overlayAnnoInfo: Opt; @observable private _marqueeing: number[] | undefined; @@ -79,6 +81,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this._webUrl = this._url); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it. } @action @@ -239,7 +242,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. } else { this._iframeClick = this._iframe ?? undefined; @@ -269,9 +272,24 @@ export class WebBox extends ViewBoxAnnotatableComponent { const iframe = this._iframe; - this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp); + let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend("") + "/corsProxy/", "") ?? this._url.toString()); + if (requrlraw !== this._url.toString()) { + if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) { + const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g); + const newsearch = matches?.lastElement()!; + if (matches) { + requrlraw = requrlraw.substring(0, requrlraw.indexOf(newsearch)); + for (let i = 1; i < Array.from(matches)?.length; i++) { + requrlraw = requrlraw.replace(matches[i], ""); + } + } + requrlraw = requrlraw.replace(/q=[^&]*/, newsearch.substring(1)).replace("search&", "search?").replace("?gbv=1", ""); + } + this.submitURL(requrlraw, undefined, true); + } if (iframe?.contentDocument) { - iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown); + iframe.contentDocument.addEventListener("pointerup", this.iframeUp); + iframe.contentDocument.addEventListener("pointerdown", this.iframeDown); this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight); setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000); const initialScroll = this._initialScroll; @@ -281,13 +299,15 @@ export class WebBox extends ViewBoxAnnotatableComponent { + iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => { let href = ""; - for (let ele = e.target; ele; ele = ele.parentElement) { + for (let ele = e.target as any; ele; ele = ele.parentElement) { href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href; } - if (href && this.webField?.origin) { - this.submitURL(href.replace(Utils.prepend(""), this.webField?.origin)); + const origin = this.webField?.origin; + if (href && origin) { + e.stopPropagation(); + setTimeout(() => this.submitURL(href.replace(Utils.prepend(""), origin))); if (this._outerRef.current) { this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop); this._outerRef.current.scrollLeft = 0; @@ -338,8 +358,15 @@ export class WebBox extends ViewBoxAnnotatableComponent([...history, this._url]); this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } return true; } return false; @@ -350,10 +377,16 @@ export class WebBox extends ViewBoxAnnotatableComponent([this._url]); else this.dataDoc[this.fieldKey + "-future"] = new List([...future, this._url]); this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); - console.log(this._urlHash); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } return true; } return false; @@ -362,9 +395,8 @@ export class WebBox extends ViewBoxAnnotatableComponent { return Math.abs(s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0)); } - @action - submitURL = (newUrl?: string, preview?: boolean) => { + submitURL = (newUrl?: string, preview?: boolean, dontUpdateIframe?: boolean) => { if (!newUrl) return; if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl; try { @@ -376,7 +408,10 @@ export class WebBox extends ViewBoxAnnotatableComponent this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); + funcs.push({ + description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => { + this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts; + if (this._iframe) { + runInAction(() => this._hackHide = true); + setTimeout(action(() => this._hackHide = false)); + } + }, icon: "snowflake" + }); funcs.push({ description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => { const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); @@ -468,18 +512,19 @@ export class WebBox extends ViewBoxAnnotatableComponent; } else if (field instanceof WebField) { - const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._url) : this._url; + const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._webUrl) : this._webUrl; view =