From abab63f89344e5bbbf0731e81e3ab7ddb0942664 Mon Sep 17 00:00:00 2001 From: ljungster Date: Tue, 15 Feb 2022 16:35:47 -0500 Subject: added commits --- src/client/views/nodes/trails/PresElementBox.tsx | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 238d025dc..b30e8cc13 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -169,6 +169,7 @@ export class PresElementBox extends ViewBoxBaseComponent { const miniView: boolean = this.toolbarWidth <= 100; const activeItem = this.rootDoc; @@ -291,6 +292,7 @@ export class PresElementBox extends ViewBoxBaseComponent Date: Sat, 12 Mar 2022 07:44:01 -0500 Subject: attempting to add note-taking I think this has something to do with the view not being rendered in novice mode. Assuming this is an issue in CollectionMenu.tsx. Essentially what I did was add a note-taking view wherever I found a stacking view (via global search) --- src/client/documents/Documents.ts | 4 + src/client/util/CurrentUserUtils.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/PropertiesButtons.tsx | 3 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/StyleProvider.tsx | 2 +- src/client/views/collections/CollectionMenu.tsx | 125 +++- .../views/collections/CollectionNoteTakingView.tsx | 739 +++++++++++++++++++++ .../CollectionNoteTakingViewFieldColumn.tsx | 359 ++++++++++ .../views/collections/CollectionStackingView.tsx | 102 ++- .../CollectionStackingViewFieldColumn.tsx | 7 + src/client/views/collections/CollectionView.tsx | 7 +- src/client/views/collections/TreeView.tsx | 1 + src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 3 +- src/client/views/nodes/button/FontIconBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 1 + src/fields/Doc.ts | 2 +- 17 files changed, 1343 insertions(+), 21 deletions(-) create mode 100644 src/client/views/collections/CollectionNoteTakingView.tsx create mode 100644 src/client/views/collections/CollectionNoteTakingViewFieldColumn.tsx (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index df573a377..0014c0c92 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -847,6 +847,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Stacking }, id, undefined, protoId); } + export function NoteTakingDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.NoteTaking }, id, undefined, protoId); + } + export function MulticolumnDocument(documents: Array, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Multicolumn }); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a8b0da369..8cb735d10 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -406,6 +406,7 @@ export class CurrentUserUtils { title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc }[] { + //TODO: we may need to add in note-teking view here if (doc.emptyPresentation === undefined) { doc.emptyPresentation = Docs.Create.PresDocument(new List(), { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List(["system"]) }); @@ -1058,7 +1059,7 @@ export class CurrentUserUtils { CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, - CollectionViewType.Grid], + CollectionViewType.NoteTaking, CollectionViewType.Grid], script: 'setView', }, // Always show { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index aa9318310..2293da6c8 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -216,7 +216,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV if (targetDoc) { TabDocView.PinDoc(targetDoc); const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; - const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking || targetDoc._viewType === CollectionViewType.NoteTaking; const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); if (scrollable) { const scroll = targetDoc._scrollTop; diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index f9dab9f82..3cb57fc7c 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -208,7 +208,8 @@ export class PropertiesButtons extends React.Component<{}, {}> { const isInk = layoutField instanceof InkField; const isMap = this.selectedDoc?.type === DocumentType.MAP; const isCollection = this.selectedDoc?.type === DocumentType.COL; - const isStacking = this.selectedDoc?._viewType === CollectionViewType.Stacking; + //TODO: will likely need to create separate note-taking view type here + const isStacking = this.selectedDoc?._viewType === CollectionViewType.Stacking || this.selectedDoc?._viewType === CollectionViewType.NoteTaking; const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform; const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree; const isTabView = this.selectedTabView; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 25b1381fe..df0455fa9 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1349,7 +1349,7 @@ export class PropertiesView extends React.Component { const type = PresBox.Instance.activeItem?.type; const viewType = PresBox.Instance.activeItem?._viewType; const pannable: boolean = (type === DocumentType.COL && viewType === CollectionViewType.Freeform) || type === DocumentType.IMG; - const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking; + const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking || viewType === CollectionViewType.NoteTaking; return
Presentation diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 8ee673115..0b1cb3be0 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -112,7 +112,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; - case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || + case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 131f5ba46..1ab4e9207 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -278,6 +278,7 @@ export class CollectionViewBaseChrome extends React.Component); case CollectionViewType.Stacking: return (); + case CollectionViewType.NoteTaking: return (); case CollectionViewType.Schema: return (); case CollectionViewType.Tree: return (); case CollectionViewType.Masonry: return (); @@ -496,7 +499,7 @@ export class CollectionViewBaseChrome extends React.Component { + @observable private _currentKey: string = ""; + @observable private suggestions: string[] = []; + + get document() { return this.props.docView.props.Document; } + + @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; } + @computed get pivotField() { return StrCast(this.document._pivotField); } + + getKeySuggestions = async (value: string): Promise => { + const val = value.toLowerCase(); + const docs = DocListCast(this.document[this.props.fieldKey]); + + if (Doc.UserDoc().noviceMode) { + if (docs instanceof Doc) { + const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || + key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || + (key[0].toUpperCase() === key[0] && key[0] !== "_")); + return keys.filter(key => key.toLowerCase().indexOf(val) > -1); + } else { + const keys = new Set(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || + key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || + (key[0]?.toUpperCase() === key[0] && key[0] !== "_")); + return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); + } + } + + if (docs instanceof Doc) { + return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1); + } else { + const keys = new Set(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); + } + } + + @action + onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { + this._currentKey = newValue; + } + + getSuggestionValue = (suggestion: string) => suggestion; + + renderSuggestion = (suggestion: string) => { + return

{suggestion}

; + } + + onSuggestionFetch = async ({ value }: { value: string }) => { + const sugg = await this.getKeySuggestions(value); + runInAction(() => { + this.suggestions = sugg; + }); + } + + @action + onSuggestionClear = () => { + this.suggestions = []; + } + + @action + setValue = (value: string) => { + this.document._pivotField = value; + return true; + } + + @action toggleSort = () => { + this.document._columnsSort = + this.document._columnsSort === "descending" ? "ascending" : + this.document._columnsSort === "ascending" ? undefined : "descending"; + } + @action resetValue = () => { this._currentKey = this.pivotField; }; + + render() { + const doctype = this.props.docView.Document.type; + const isPres: boolean = (doctype === DocumentType.PRES); + return ( + isPres ? (null) :
+
+
+ GROUP BY: +
+
+ +
+
+ this.pivotField} + autosuggestProps={ + { + resetValue: this.resetValue, + value: this._currentKey, + onChange: this.onKeyChange, + autosuggestProps: { + inputProps: + { + value: this._currentKey, + onChange: this.onKeyChange + }, + getSuggestionValue: this.getSuggestionValue, + suggestions: this.suggestions, + alwaysRenderSuggestions: true, + renderSuggestion: this.renderSuggestion, + onSuggestionsFetchRequested: this.onSuggestionFetch, + onSuggestionsClearRequested: this.onSuggestionClear + } + }} + oneLine + SetValue={this.setValue} + contents={this.pivotField ? this.pivotField : "N/A"} + /> +
+
+
+ ); + } +} + @observer export class CollectionSchemaViewChrome extends React.Component { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx new file mode 100644 index 000000000..9e16de327 --- /dev/null +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -0,0 +1,739 @@ +import React = require("react"); +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { CursorProperty } from "csstype"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { collectionSchema, documentSchema } from "../../../fields/documentSchemas"; +import { Id } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { listSpec, makeInterface } from "../../../fields/Schema"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; +import { Docs, DocUtils } from "../../documents/Documents"; +import { DragManager, dropActionType } from "../../util/DragManager"; +import { SnappingManager } from "../../util/SnappingManager"; +import { Transform } from "../../util/Transform"; +import { undoBatch } from "../../util/UndoManager"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { EditableView } from "../EditableView"; +import { LightboxView } from "../LightboxView"; +import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView"; +import { StyleProp } from "../StyleProvider"; +import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; +import "./CollectionStackingView.scss"; +import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; +import { CollectionSubView } from "./CollectionSubView"; +import { CollectionViewType } from "./CollectionView"; +import internal = require("events"); +const _global = (window /* browser */ || global /* node */) as any; + +type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; +const StackingDocument = makeInterface(collectionSchema, documentSchema); + +export type collectionStackingViewProps = { + chromeHidden?: boolean; + // view type is stacking + viewType?: CollectionViewType; + NativeWidth?: () => number; + NativeHeight?: () => number; +}; + +@observer +export class CollectionNoteTakingView extends CollectionSubView>(StackingDocument) { + // do we need a masonry grid here? We think that they are sharing data + _masonryGridRef: HTMLDivElement | null = null; + // used in a column dragger, likely due for the masonry grid view. We want to use this + _draggerRef = React.createRef(); + // Not sure what a pivot field is. Seems like we cause reaction in MobX get rid of it once we exit this view + _pivotFieldDisposer?: IReactionDisposer; + // Seems like we cause reaction in MobX get rid of our height once we exit this view + _autoHeightDisposer?: IReactionDisposer; + // keeping track of documents. Updated on internal and external drops. What's the difference? + _docXfs: { height: () => number, width: () => number, stackedDocTransform: () => Transform }[] = []; + // Doesn't look like this field is being used anywhere. Obsolete? + _columnStart: number = 0; + // map of node headers to their heights. Used in Masonry + @observable _heightMap = new Map(); + // Assuming that this is the current css cursor style + @observable _cursor: CursorProperty = "grab"; + // gets reset whenever we scroll. Not sure what it is + @observable _scroll = 0; // used to force the document decoration to update when scrolling + // does this mean whether the browser is hidden? Or is chrome something else entirely? + @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } + // it looks like this gets the column headers that Mehek was showing just now + @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } + // Still not sure what a pivot is, but it appears that we can actually filter docs somehow? + @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } + // filteredChildren is what you want to work with. It's the list of things that you're currently displaying + @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } + // how much margin we give the header + @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } + @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } + @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); } + @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } + // are we stacking or masonry? + //TODO: we might need to remove the notetaking view type + @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === (CollectionViewType.Stacking || CollectionViewType.NoteTaking); } + // this is the number of StackingViewFieldColumns that we have + @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } + // reveals a button to add a group in masonry view + @computed get showAddAGroup() { return this.pivotField && !this.chromeHidden; } + // columnWidth handles the margin on the left and right side of the documents + @computed get columnWidth() { + return Math.min(this.props.PanelWidth() - 2 * this.xMargin, + this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); + } + + @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } + + constructor(props: any) { + super(props); + + if (this.columnHeaders === undefined) { + // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out? + // here we're making an empty list of column headers (again, what Mehek showed us) + this.layoutDoc._columnHeaders = new List(); + } + } + + // TODO: plj - these are the children + children = (docs: Doc[]) => { + //TODO: can somebody explain me to what exactly TraceMobX is? + TraceMobx(); + // appears that we are going to reset the _docXfs. TODO: what is Xfs? + this._docXfs.length = 0; + // Go through each of the documents that are contained + return docs.map((d, i) => { + const height = () => this.getDocHeight(d); + const width = () => this.getDocWidth(d); + // assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns + const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); + // just getting the style + const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; + // So we're choosing whether we're going to render a column or a masonry doc + return
+ {/*
*/} + {/* We'll want to add an onPointerDown that uses DragManager.DocumentDragData + -- we also want to remember to preventDefault (so other drag events are not recognized over this one) + -- Design discussion as to whether we want dragging to be on the document itself or with a drag button + -- Do we want clicking on this button to do anything as well? + -- Design Question: Schema view also has the notion of a drag manager (different from this one), do we want + the same functionality? + -- Problem: This only shows when the outer container is selected... + */} + {/*
+ e.stopPropagation()} /> +
+
*/} + {/* some sort of filtering is being done here to actually get layout and make the doc look pretty*/} + {this.getDisplayDoc(d, width)} +
+ }); + } + @action + setDocHeight = (key: string, sectionHeight: number) => { + this._heightMap.set(key, sectionHeight); + } + + // is sections that all collections inherit? I think this is how we show the masonry/columns + //TODO: this seems important + get Sections() { + // appears that pivot field IS actually for sorting + if (!this.pivotField || this.columnHeaders instanceof Promise) return new Map(); + + if (this.columnHeaders === undefined) { + setTimeout(() => this.layoutDoc._columnHeaders = new List(), 0); + return new Map(); + } + const columnHeaders = Array.from(this.columnHeaders); + const fields = new Map(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); + let changed = false; + this.filteredChildren.map(d => { + const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object; + // the next five lines ensures that floating point rounding errors don't create more than one section -syip + const parsed = parseInt(sectionValue.toString()); + const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue; + + // look for if header exists already + const existingHeader = columnHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`)); + if (existingHeader) { + fields.get(existingHeader)!.push(d); + } + else { + const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`); + fields.set(newSchemaHeader, [d]); + columnHeaders.push(newSchemaHeader); + changed = true; + } + }); + // remove all empty columns if hideHeadings is set + // we will want to have something like this, so that we can hide columns and add them back in + if (this.layoutDoc._columnsHideIfEmpty) { + Array.from(fields.keys()).filter(key => !fields.get(key)!.length).map(header => { + fields.delete(header); + columnHeaders.splice(columnHeaders.indexOf(header), 1); + changed = true; + }); + } + changed && setTimeout(action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), 0); + return fields; + } + + componentDidMount() { + super.componentDidMount?.(); + + // reset section headers when a new filter is inputted + this._pivotFieldDisposer = reaction( + () => this.pivotField, + () => this.layoutDoc._columnHeaders = new List() + ); + //TODO: where the heck are we getting filters from? + this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, + autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + this.headerMargin + (this.isStackingView ? + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) : + this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0))))); + } + + componentWillUnmount() { + super.componentWillUnmount(); + this._pivotFieldDisposer?.(); + this._autoHeightDisposer?.(); + } + + @action + moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { + return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; + } + createRef = (ele: HTMLDivElement | null) => { + this._masonryGridRef = ele; + this.createDashEventsTarget(ele!); //so the whole grid is the drop target? + } + + @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); } + @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } + + addDocTab = (doc: Doc, where: string) => { + if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + this.dataDoc[this.props.fieldKey] = new List([doc]); + return true; + } + return this.props.addDocTab(doc, where); + } + + scrollToBottom = () => { + smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); + } + + // let's dive in and get the actual document we want to drag/move around + focusDocument = (doc: Doc, options?: DocFocusOptions) => { + Doc.BrushDoc(doc); + + let focusSpeed = 0; + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]); + if (found) { + const top = found.getBoundingClientRect().top; + const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); + if (Math.floor(localTop[1]) !== 0) { + smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + } + } + const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing; + this.props.focus(this.rootDoc, { + willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) => + new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) + }); + } + + styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { + if (property === StyleProp.Opacity && doc) { + if (this.props.childOpacity) { + return this.props.childOpacity(); + } + if (this.Document._currentFrame !== undefined) { + return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity; + } + } + return this.props.styleProvider?.(doc, props, property); + } + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + + // this is what renders the document that you see on the screen + // called in Children: this actually adds a document to our children list + getDisplayDoc(doc: Doc, width: () => number) { + const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; + const height = () => this.getDocHeight(doc); + + let dref: Opt; + const stackedDocTransform = () => this.getDocTransform(doc, dref); + this._docXfs.push({ stackedDocTransform, width, height }); + //DocumentView is how the node will be rendered + return dref = r || undefined} + Document={doc} + DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} + renderDepth={this.props.renderDepth + 1} + PanelWidth={width} + PanelHeight={height} + styleProvider={this.styleProvider} + layerProvider={this.props.layerProvider} + docViewPath={this.props.docViewPath} + fitWidth={this.props.childFitWidth} + isContentActive={emptyFunction} + isDocumentActive={this.isContentActive} + LayoutTemplate={this.props.childLayoutTemplate} + LayoutTemplateString={this.props.childLayoutString} + freezeDimensions={this.props.childFreezeDimensions} + NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox + NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined} + dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined} + dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} + rootSelected={this.rootSelected} + showTitle={this.props.childShowTitle} + dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} + onClick={this.onChildClickHandler} + onDoubleClick={this.onChildDoubleClickHandler} + ScreenToLocalTransform={stackedDocTransform} + focus={this.focusDocument} + docFilters={this.childDocFilters} + hideDecorationTitle={this.props.childHideDecorationTitle?.()} + hideResizeHandles={this.props.childHideResizeHandles?.()} + hideTitle={this.props.childHideTitle?.()} + docRangeFilters={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + removeDocument={this.props.removeDocument} + contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + addDocTab={this.addDocTab} + bringToFront={returnFalse} + scriptContext={this.props.scriptContext} + pinToPres={this.props.pinToPres} + />; + } + + getDocTransform(doc: Doc, dref?: DocumentView) { + const y = this._scroll; // required for document decorations to update when the text box container is scrolled + const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); + // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off + return new Transform(- translateX + (dref?.centeringX || 0), - translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); + } + getDocWidth(d?: Doc) { + if (!d) return 0; + const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + // TODO: pj - replace with a better way to calculate the margin + let margin = 25; + d.margin = 25; + if (this.columnWidth < 150){ + margin = 0; + } + const maxWidth = (this.columnWidth / this.numGroupColumns) - (margin * 2); + if (!this.layoutDoc._columnsFill && !(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d))) { + return Math.min(d[WidthSym](), maxWidth); + } + return maxWidth; + } + getDocHeight(d?: Doc) { + if (!d || d.hidden) return 0; + const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc; + const maxHeight = (lim => lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1)); + const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); + const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); + if (nw && nh) { + const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); + const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid); + return Math.min( + maxHeight, + docWid * nh / nw); + } + const childHeight = NumCast(childLayoutDoc._height); + const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin; + return Math.min(childHeight, maxHeight, panelHeight); + } + + // This following three functions must be from the view Mehek showed + columnDividerDown = (e: React.PointerEvent) => { + runInAction(() => this._cursor = "grabbing"); + setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction); + } + @action + onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { + this.layoutDoc._columnWidth = Math.max(10, this.columnWidth + delta[0]); + return false; + } + + @computed get columnDragger() { + return
+ +
; + } + + // TODO: plj + @action + onPointerOver = (e: React.PointerEvent) => { + // console.log("hovering over something") + if (DragManager.docsBeingDragged.length) { + // essentially copying code from onInternalDrop for this: + const doc = DragManager.docsBeingDragged[0] + // console.log(doc[LayoutSym]()) + + console.log(doc[DataSym]) + console.log(Doc.IndexOf(doc, this.childDocs)) + + } + + + } + + //used in onPointerOver to swap two nodes in the rendered filtered children list + swapNodes = (i: number, j: number) => { + + } + + //plj added this + @action + onPointerDown = (e: React.PointerEvent) => { + + } + + // TODO: plj - look at this. Start with making changes to db, and then transition to client side + @undoBatch + @action + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + // Fairly confident that this is where the swapping of nodes in the various arrays happens + console.log('drop') + const where = [de.x, de.y]; + // start at -1 until we're sure we want to add it to the column + let dropInd = -1; + let dropAfter = 0; + if (de.complete.docDragData) { + // going to re-add the docs to the _docXFs based on position of where we just dropped + this._docXfs.map((cd, i) => { + const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); + if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) { + dropInd = i; + const axis = this.isStackingView ? 1 : 0; + dropAfter = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0; + } + }); + const oldDocs = this.childDocs.length; + if (super.onInternalDrop(e, de)) { + // check to see if we actually need anything to the new column of nodes (if droppedDocs != empty) + const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= oldDocs); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note). + const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments; // if nothing was added to the end of the list, then presumably the dropped documents were already in the list, but possibly got reordered so we use them. + + const docs = this.childDocList; + // reset drag manager docs, because we just dropped + DragManager.docsBeingDragged = []; + // still figuring out where to add the document + if (docs && newDocs.length) { + const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; + const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); + newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); + docs.splice(insertInd - offset, 0, ...newDocs); + } + } + } // it seems like we're creating a link here. Weird. I didn't know that you could establish links by dragging + else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { + const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" }); + this.props.addDocument?.(source); + de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed + e.stopPropagation(); + } + else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); + return false; + } + + @undoBatch + internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) { + const dropCreator = annoDragData.dropDocCreator; + annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => { + const dropDoc = dropCreator(annotationOn); + return dropDoc || this.rootDoc; + }; + return true; + } + + @undoBatch + @action + //What is the difference between internal and external drop?? Does internal mean we're dropping inside of a collection? + // I take it back: external drop means we took it out of column/collection that we were just in + onExternalDrop = async (e: React.DragEvent): Promise => { + console.log('external drop') + const where = [e.clientX, e.clientY]; + let targInd = -1; + this._docXfs.map((cd, i) => { + const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); + if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) { + targInd = i; + } + }); + super.onExternalDrop(e, {}, () => { + if (targInd !== -1) { + const newDoc = this.childDocs[this.childDocs.length - 1]; + const docs = this.childDocList; + if (docs) { + docs.splice(docs.length - 1, 1); + docs.splice(targInd, 0, newDoc); + } + } + }); + } + // sections are important + headings = () => Array.from(this.Sections); + refList: any[] = []; + // what a section looks like if we're in stacking view + sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { + const key = this.pivotField; + let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + if (this.pivotField) { + const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); + if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { + type = types[0]; + } + } + //TODO: I think that we only have one of these atm + return this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={ref => { + if (ref) { + this.refList.push(ref); + this.observer = new _global.ResizeObserver(action((entries: any) => { + if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + const height = this.headerMargin + + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); + if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { + this.props.setHeight(height); + } + } + })); + this.observer.observe(ref); + } + }} + addDocument={this.addDocument} + chromeHidden={this.chromeHidden} + columnHeaders={this.columnHeaders} + Document={this.props.Document} + DataDoc={this.props.DataDoc} + renderChildren={this.children} + columnWidth={this.columnWidth} + numGroupColumns={this.numGroupColumns} + gridGap={this.gridGap} + pivotField={this.pivotField} + key={heading?.heading ?? ""} + headings={this.headings} + heading={heading?.heading ?? ""} + headingObject={heading} + docList={docList} + yMargin={this.yMargin} + type={type} + createDropTarget={this.createDashEventsTarget} + screenToLocalTransform={this.props.ScreenToLocalTransform} + />; + } + + // what a section looks like if we're in masonry. Shouldn't actually need to use this. + sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { + const key = this.pivotField; + let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); + if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { + type = types[0]; + } + const rows = () => !this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, + Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + return this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={(ref) => { + if (ref) { + this.refList.push(ref); + this.observer = new _global.ResizeObserver(action((entries: any) => { + if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); + this.props.setHeight(this.headerMargin + height); + } + })); + this.observer.observe(ref); + } + }} + key={heading ? heading.heading : ""} + rows={rows} + headings={this.headings} + heading={heading ? heading.heading : ""} + headingObject={heading} + docList={docList} + parent={this} + type={type} + createDropTarget={this.createDashEventsTarget} + screenToLocalTransform={this.props.ScreenToLocalTransform} + setDocHeight={this.setDocHeight} + />; + } + + @action + // What are we adding a group to? + addGroup = (value: string) => { + if (value && this.columnHeaders) { + const schemaHdrField = new SchemaHeaderField(value); + this.columnHeaders.push(schemaHdrField); + DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]); + return true; + } + return false; + } + + sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => { + const descending = StrCast(this.layoutDoc._columnsSort) === "descending"; + const firstEntry = descending ? b : a; + const secondEntry = descending ? a : b; + return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1; + } + + onContextMenu = (e: React.MouseEvent): void => { + // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout + if (!e.isPropagationStopped()) { + const subItems: ContextMenuProps[] = []; + subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" }); + subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); + subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" }); + ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" }); + } + } + + // + @computed get renderedSections() { + TraceMobx(); + let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]]; + if (this.pivotField) { + const entries = Array.from(this.Sections.entries()); + sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries; + } + // a section will have a header and a list of docs. Ok cool. + return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0)); + } + + @computed get buttonMenu() { + const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null); + // TODO:glr Allow support for multiple buttons + if (menuDoc) { + const width: number = NumCast(menuDoc._width, 30); + const height: number = NumCast(menuDoc._height, 30); + console.log(menuDoc.title, width, height); + return (
+ 35} + PanelHeight={() => 35} + renderDepth={this.props.renderDepth} + focus={emptyFunction} + styleProvider={this.props.styleProvider} + layerProvider={this.props.layerProvider} + docViewPath={returnEmptyDoclist} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={this.props.docFilters} + docRangeFilters={this.props.docRangeFilters} + searchFilterDocs={this.props.searchFilterDocs} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + /> +
+ ); + } + } + + + @computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); } + @computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); } + + @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; } + + @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); } + observer: any; + render() { + TraceMobx(); + const editableViewProps = { + GetValue: () => "", + SetValue: this.addGroup, + // I don't recall ever seeing this add a group button + contents: "+ ADD A GROUP" + }; + const buttonMenu = this.rootDoc.buttonMenu; + const noviceExplainer = this.rootDoc.explainer; + return ( + <> + {buttonMenu || noviceExplainer ?
+ {buttonMenu ? this.buttonMenu : null} + {Doc.UserDoc().noviceMode && noviceExplainer ? +
+ {noviceExplainer} +
+ : null + } +
: null} +
+
this._scroll = e.currentTarget.scrollTop)} + onPointerOver={this.onPointerOver} + onPointerDown={this.onPointerDown} + onDrop={this.onExternalDrop.bind(this)} + onContextMenu={this.onContextMenu} + // Todo: what is wheel? Are we talking about a mouse wheel? + onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} > + {/* so it appears that we are actually rendering the sections. Maybe this is what we're looking for? */} + {this.renderedSections} + {/* I think that showAddGroup must be passed in as false, which is why we can't find what Mehek showed + Or it's because we aren't passing a pivot field */} + {!this.showAddAGroup ? (null) : +
+ +
} + {/* {this.chromeHidden || !this.props.isSelected() ? (null) : + } */} +
+
+ + + ); + } +} diff --git a/src/client/views/collections/CollectionNoteTakingViewFieldColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewFieldColumn.tsx new file mode 100644 index 000000000..c191445e7 --- /dev/null +++ b/src/client/views/collections/CollectionNoteTakingViewFieldColumn.tsx @@ -0,0 +1,359 @@ +import React = require("react"); +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; +import { RichTextField } from "../../../fields/RichTextField"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { ImageField } from "../../../fields/URLField"; +import { TraceMobx } from "../../../fields/util"; +import { emptyFunction, setupMoveUpEvents, returnFalse, returnEmptyString } from "../../../Utils"; +import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { DragManager } from "../../util/DragManager"; +import { SnappingManager } from "../../util/SnappingManager"; +import { Transform } from "../../util/Transform"; +import { undoBatch } from "../../util/UndoManager"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { EditableView } from "../EditableView"; +import "./CollectionStackingView.scss"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; +import { Id } from "../../../fields/FieldSymbols"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; + +// So this is how we are storing a column +interface CSVFieldColumnProps { + Document: Doc; + DataDoc: Opt; + docList: Doc[]; + heading: string; + pivotField: string; + chromeHidden?: boolean; + columnHeaders: SchemaHeaderField[] | undefined; + headingObject: SchemaHeaderField | undefined; + yMargin: number; + columnWidth: number; + numGroupColumns: number; + gridGap: number; + type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; + headings: () => object[]; + // I think that stacking view actually has a single column and then supposedly you can add more columns? Unsure + renderChildren: (docs: Doc[]) => JSX.Element[]; + addDocument: (doc: Doc | Doc[]) => boolean; + createDropTarget: (ele: HTMLDivElement) => void; + screenToLocalTransform: () => Transform; + observeHeight: (myref: any) => void; + unobserveHeight: (myref: any) => void; +} + +@observer +export class CollectionStackingViewFieldColumn extends React.Component { + @observable private _background = "inherit"; + + private dropDisposer?: DragManager.DragDropDisposer; + private _headerRef: React.RefObject = React.createRef(); + + @observable _paletteOn = false; + @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; + @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + _ele: HTMLElement | null = null; + + // This is likely similar to what we will be doing. Why do we need to make these refs? + // is that the only way to have drop targets? + createColumnDropRef = (ele: HTMLDivElement | null) => { + this.dropDisposer?.(); + if (ele) { + this._ele = ele; + this.props.observeHeight(ele); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this)); + } + } + componentWillUnmount() { + this.props.unobserveHeight(this._ele); + } + + //TODO: what is scripting? I found it in SetInPlace def but don't know what that is + @undoBatch + columnDrop = action((e: Event, de: DragManager.DropEvent) => { + const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; + drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false)); + }); + getValue = (value: string): any => { + const parsed = parseInt(value); + if (!isNaN(parsed)) return parsed; + if (value.toLowerCase().indexOf("true") > -1) return true; + if (value.toLowerCase().indexOf("false") > -1) return false; + return value; + } + + @action + headingChanged = (value: string, shiftDown?: boolean) => { + const castedValue = this.getValue(value); + if (castedValue) { + if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { + return false; + } + this.props.docList.forEach(d => d[this.props.pivotField] = castedValue); + if (this.props.headingObject) { + this.props.headingObject.setHeading(castedValue.toString()); + this._heading = this.props.headingObject.heading; + } + return true; + } + return false; + } + + @action + changeColumnColor = (color: string) => { + this.props.headingObject?.setColor(color); + this._color = color; + } + + @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4"); + @action pointerLeave = () => this._background = "inherit"; + textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true); + + @action + addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { + if (!value && !forceEmptyNote) return false; + const key = this.props.pivotField; + const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _fitWidth: true, title: value, _autoHeight: true }); + newDoc[key] = this.getValue(this.props.heading); + const maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; + newDoc.heading = heading; + FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " "; + return this.props.addDocument?.(newDoc) || false; + } + + @action + deleteColumn = () => { + this.props.docList.forEach(d => d[this.props.pivotField] = undefined); + if (this.props.columnHeaders && this.props.headingObject) { + const index = this.props.columnHeaders.indexOf(this.props.headingObject); + this.props.columnHeaders.splice(index, 1); + } + } + + @action + collapseSection = () => { + this.props.headingObject?.setCollapsed(!this.props.headingObject.collapsed); + this.toggleVisibility(); + } + + headerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); + + //TODO: I think this is where I'm supposed to edit stuff + startDrag = (e: PointerEvent, down: number[], delta: number[]) => { + // is MakeAlias a way to make a copy of a doc without rendering it? + const alias = Doc.MakeAlias(this.props.Document); + alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1); + alias._pivotField = undefined; + let value = this.getValue(this._heading); + value = typeof value === "string" ? `"${value}"` : value; + alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name }); + if (alias.viewSpecScript) { + DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY); + return true; + } + return false; + } + + renderColorPicker = () => { + const gray = "#f1efeb"; + const selected = this.props.headingObject ? this.props.headingObject.color : gray; + const colors = ["pink2", "purple4", "bluegreen1", "yellow4", "gray", "red2", "bluegreen7", "bluegreen5", "orange1"]; + return
+
+ {colors.map(col => { + const palette = PastelSchemaPalette.get(col); + return
this.changeColumnColor(palette!)} />; + })} +
+
; + } + + renderMenu = () => { + return
+
+
{ })}>Add options here
+
+
; + } + + @observable private collapsed: boolean = false; + + private toggleVisibility = action(() => this.collapsed = !this.collapsed); + + menuCallback = (x: number, y: number) => { + ContextMenu.Instance.clearItems(); + const layoutItems: ContextMenuProps[] = []; + const docItems: ContextMenuProps[] = []; + const dataDoc = this.props.DataDoc || this.props.Document; + + DocUtils.addDocumentCreatorMenuItems((doc) => { + FormattedTextBox.SelectOnLoad = doc[Id]; + return this.props.addDocument?.(doc); + }, this.props.addDocument, x, y, true); + + Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey => + docItems.push({ + description: ":" + fieldKey, event: () => { + const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document)); + if (created) { + if (this.props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this.props.Document); + } + return this.props.addDocument?.(created); + } + }, icon: "compress-arrows-alt" + })); + Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => DocListCast(dataDoc[fieldKey]).length).map(fieldKey => + docItems.push({ + description: ":" + fieldKey, event: () => { + const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); + if (created) { + const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document; + if (container.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, container); + return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); + } + return this.props.addDocument?.(created) || false; + } + }, icon: "compress-arrows-alt" + })); + !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" }); + !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" }); + ContextMenu.Instance.setDefaultItem("::", (name: string): void => { + Doc.GetProto(this.props.Document)[name] = ""; + const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true }); + if (created) { + if (this.props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this.props.Document); + } + this.props.addDocument?.(created); + } + }); + const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y); + ContextMenu.Instance.displayMenu(x, y, undefined, true); + } + @computed get innards() { + TraceMobx(); + const key = this.props.pivotField; + const headings = this.props.headings(); + const heading = this._heading; + const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin; + const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); + const evContents = heading ? heading : this.props?.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; + const headingView = this.props.headingObject ? +
+
+ {/* the default bucket (no key value) has a tooltip that describes what it is. + Further, it does not have a color and cannot be deleted. */} +
+ evContents} + SetValue={this.headingChanged} + contents={evContents} + oneLine={true} + toggle={this.toggleVisibility} /> + {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : +
+ + {this._paletteOn ? this.renderColorPicker() : (null)} +
+ } + {} + {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : +
+ + + +
+ } +
+
: (null); + const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `; + const type = this.props.Document.type; + return <> + {this.props.Document._columnsHideIfEmpty ? (null) : headingView} + { + this.collapsed ? (null) : +
+
+ {this.props.renderChildren(this.props.docList)} +
+ {!this.props.chromeHidden && type !== DocumentType.PRES ? + // TODO: this is the "new" button: see what you can work with here + // change cursor to pointer for this, and update dragging cursor + //TODO: there is a bug that occurs when adding a freeform document and trying to move it around + //TODO: would be great if there was additional space beyond the frame, so that you can actually see your + // bottom note + //TODO: ok, so we are using a single column, and this is it! +
+ } + toggle={this.toggleVisibility} + menuCallback={this.menuCallback} + /> +
: null} +
+ } + ; + } + + + render() { + TraceMobx(); + const headings = this.props.headings(); + const heading = this._heading; + const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); + return ( +
+ {this.innards} +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 419b9a943..9d83e13de 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { CursorProperty } from "csstype"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../fields/Doc"; +import { DataSym, Doc, HeightSym, Opt, WidthSym, DocListCast, AclSym, FieldsSym, Initializing, LayoutSym, DirectLinksSym } from "../../../fields/Doc"; import { collectionSchema, documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; @@ -31,6 +31,7 @@ import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; import { FontIconBox } from "../nodes/button/FontIconBox"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import internal = require("events"); const _global = (window /* browser */ || global /* node */) as any; type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; @@ -38,6 +39,7 @@ const StackingDocument = makeInterface(collectionSchema, documentSchema); export type collectionStackingViewProps = { chromeHidden?: boolean; + // view type is stacking viewType?: CollectionViewType; NativeWidth?: () => number; NativeHeight?: () => number; @@ -45,53 +47,78 @@ export type collectionStackingViewProps = { @observer export class CollectionStackingView extends CollectionSubView>(StackingDocument) { + // do we need a masonry grid here? We think that they are sharing data _masonryGridRef: HTMLDivElement | null = null; + // used in a column dragger, likely due for the masonry grid view. We want to use this _draggerRef = React.createRef(); + // Not sure what a pivot field is. Seems like we cause reaction in MobX get rid of it once we exit this view _pivotFieldDisposer?: IReactionDisposer; + // Seems like we cause reaction in MobX get rid of our height once we exit this view _autoHeightDisposer?: IReactionDisposer; + // keeping track of documents. Updated on internal and external drops. What's the difference? _docXfs: { height: () => number, width: () => number, stackedDocTransform: () => Transform }[] = []; + // Doesn't look like this field is being used anywhere. Obsolete? _columnStart: number = 0; + // map of node headers to their heights. Used in Masonry @observable _heightMap = new Map(); + // Assuming that this is the current css cursor style @observable _cursor: CursorProperty = "grab"; + // gets reset whenever we scroll. Not sure what it is @observable _scroll = 0; // used to force the document decoration to update when scrolling + // does this mean whether the browser is hidden? Or is chrome something else entirely? @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } + // it looks like this gets the column headers that Mehek was showing just now @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } + // Still not sure what a pivot is, but it appears that we can actually filter docs somehow? @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } + // filteredChildren is what you want to work with. It's the list of things that you're currently displaying @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } + // how much margin we give the header @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); } @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } - @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking; } + // are we stacking or masonry? + @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === (CollectionViewType.Stacking || CollectionViewType.NoteTaking); } + // this is the number of StackingViewFieldColumns that we have @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } + // reveals a button to add a group in masonry view @computed get showAddAGroup() { return this.pivotField && !this.chromeHidden; } // columnWidth handles the margin on the left and right side of the documents @computed get columnWidth() { return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); } + @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } constructor(props: any) { super(props); if (this.columnHeaders === undefined) { + // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out? + // here we're making an empty list of column headers (again, what Mehek showed us) this.layoutDoc._columnHeaders = new List(); } } // TODO: plj - these are the children children = (docs: Doc[]) => { + //TODO: can somebody explain me to what exactly TraceMobX is? TraceMobx(); + // appears that we are going to reset the _docXfs. TODO: what is Xfs? this._docXfs.length = 0; // Go through each of the documents that are contained return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); + // assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); + // just getting the style const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; + // So we're choosing whether we're going to render a column or a masonry doc return
-
+ {/*
*/} {/* We'll want to add an onPointerDown that uses DragManager.DocumentDragData -- we also want to remember to preventDefault (so other drag events are not recognized over this one) -- Design discussion as to whether we want dragging to be on the document itself or with a drag button @@ -100,11 +127,12 @@ export class CollectionStackingView extends CollectionSubView - e.stopPropagation()} /> + {/*
+ e.stopPropagation()} />
-
- {this.getDisplayDoc(d, width)} +
*/} + {/* some sort of filtering is being done here to actually get layout and make the doc look pretty*/} + {this.getDisplayDoc(d, width)}
}); } @@ -113,7 +141,10 @@ export class CollectionStackingView extends CollectionSubView(); if (this.columnHeaders === undefined) { @@ -142,6 +173,7 @@ export class CollectionStackingView extends CollectionSubView !fields.get(key)!.length).map(header => { fields.delete(header); @@ -161,6 +193,7 @@ export class CollectionStackingView extends CollectionSubView this.pivotField, () => this.layoutDoc._columnHeaders = new List() ); + //TODO: where the heck are we getting filters from? this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + (this.isStackingView ? @@ -198,6 +231,7 @@ export class CollectionStackingView extends CollectionSubView { Doc.BrushDoc(doc); @@ -229,6 +263,9 @@ export class CollectionStackingView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); + + // this is what renders the document that you see on the screen + // called in Children: this actually adds a document to our children list getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); @@ -236,6 +273,7 @@ export class CollectionStackingView extends CollectionSubView; const stackedDocTransform = () => this.getDocTransform(doc, dref); this._docXfs.push({ stackedDocTransform, width, height }); + //DocumentView is how the node will be rendered return dref = r || undefined} Document={doc} DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} @@ -322,6 +360,7 @@ export class CollectionStackingView extends CollectionSubView { runInAction(() => this._cursor = "grabbing"); setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction); @@ -342,20 +381,43 @@ export class CollectionStackingView extends CollectionSubView { - if (DragManager.docsBeingDragged.length){ - console.log(DragManager.docsBeingDragged[0].title) + // console.log("hovering over something") + if (DragManager.docsBeingDragged.length) { + // essentially copying code from onInternalDrop for this: + const doc = DragManager.docsBeingDragged[0] + // console.log(doc[LayoutSym]()) + + console.log(doc[DataSym]) + console.log(Doc.IndexOf(doc, this.childDocs)) + } + + + } + + //used in onPointerOver to swap two nodes in the rendered filtered children list + swapNodes = (i: number, j: number) => { + + } + + //plj added this + @action + onPointerDown = (e: React.PointerEvent) => { + } - // TODO: plj - look at this + // TODO: plj - look at this. Start with making changes to db, and then transition to client side @undoBatch @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + // Fairly confident that this is where the swapping of nodes in the various arrays happens console.log('drop') const where = [de.x, de.y]; + // start at -1 until we're sure we want to add it to the column let dropInd = -1; let dropAfter = 0; if (de.complete.docDragData) { + // going to re-add the docs to the _docXFs based on position of where we just dropped this._docXfs.map((cd, i) => { const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); @@ -367,11 +429,14 @@ export class CollectionStackingView extends CollectionSubView ind >= oldDocs); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note). const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments; // if nothing was added to the end of the list, then presumably the dropped documents were already in the list, but possibly got reordered so we use them. const docs = this.childDocList; + // reset drag manager docs, because we just dropped DragManager.docsBeingDragged = []; + // still figuring out where to add the document if (docs && newDocs.length) { const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); @@ -379,7 +444,7 @@ export class CollectionStackingView extends CollectionSubView => { console.log('external drop') const where = [e.clientX, e.clientY]; @@ -424,8 +491,10 @@ export class CollectionStackingView extends CollectionSubView Array.from(this.Sections); refList: any[] = []; + // what a section looks like if we're in stacking view sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const key = this.pivotField; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; @@ -435,6 +504,7 @@ export class CollectionStackingView extends CollectionSubView this.refList.splice(this.refList.indexOf(ref), 1)} observeHeight={ref => { @@ -475,6 +545,7 @@ export class CollectionStackingView extends CollectionSubView; } + // what a section looks like if we're in masonry. Shouldn't actually need to use this. sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { const key = this.pivotField; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; @@ -517,6 +588,7 @@ export class CollectionStackingView extends CollectionSubView { if (value && this.columnHeaders) { const schemaHdrField = new SchemaHeaderField(value); @@ -545,6 +617,7 @@ export class CollectionStackingView extends CollectionSubView this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0)); } @@ -609,6 +683,7 @@ export class CollectionStackingView extends CollectionSubView "", SetValue: this.addGroup, + // I don't recall ever seeing this add a group button contents: "+ ADD A GROUP" }; const buttonMenu = this.rootDoc.buttonMenu; @@ -634,10 +709,15 @@ export class CollectionStackingView extends CollectionSubView this._scroll = e.currentTarget.scrollTop)} onPointerOver={this.onPointerOver} + onPointerDown={this.onPointerDown} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} + // Todo: what is wheel? Are we talking about a mouse wheel? onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} > + {/* so it appears that we are actually rendering the sections. Maybe this is what we're looking for? */} {this.renderedSections} + {/* I think that showAddGroup must be passed in as false, which is why we can't find what Mehek showed + Or it's because we aren't passing a pivot field */} {!this.showAddAGroup ? (null) :
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 1a27be764..c191445e7 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -26,6 +26,7 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; +// So this is how we are storing a column interface CSVFieldColumnProps { Document: Doc; DataDoc: Opt; @@ -41,6 +42,7 @@ interface CSVFieldColumnProps { gridGap: number; type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; headings: () => object[]; + // I think that stacking view actually has a single column and then supposedly you can add more columns? Unsure renderChildren: (docs: Doc[]) => JSX.Element[]; addDocument: (doc: Doc | Doc[]) => boolean; createDropTarget: (ele: HTMLDivElement) => void; @@ -61,6 +63,8 @@ export class CollectionStackingViewFieldColumn extends React.Component { this.dropDisposer?.(); if (ele) { @@ -73,6 +77,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; @@ -146,6 +151,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { + // is MakeAlias a way to make a copy of a doc without rendering it? const alias = Doc.MakeAlias(this.props.Document); alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1); alias._pivotField = undefined; @@ -314,6 +320,7 @@ export class CollectionStackingViewFieldColumn extends React.Component ; case CollectionViewType.Carousel3D: return ; case CollectionViewType.Stacking: return ; + case CollectionViewType.NoteTaking: return ; case CollectionViewType.Masonry: return ; case CollectionViewType.Time: return ; case CollectionViewType.Grid: return ; @@ -158,6 +162,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent func(CollectionViewType.Schema), icon: "th-list" }); subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" }); subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" }); + subItems.push({ description: "Note taking", event: () => func(CollectionViewType.NoteTaking)._autoHeight = true, icon: "ellipsis-v" }); subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" }); subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" }); subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" }); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index eedb353e3..f30a6ac67 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -214,6 +214,7 @@ export class TreeView extends React.Component { document.removeEventListener("pointerup", this.onDragUp, true); document.removeEventListener("pointermove", this.onDragMove, true); } + // TODO: Parker look at this onDragMove = (e: PointerEvent): void => { Doc.UnBrushDoc(this.dataDoc); const pt = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index 0d5fedb7b..5e5f6cd74 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -7,6 +7,7 @@ import { Id } from '../../../../fields/FieldSymbols'; import { emptyFunction, OmitKeys, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DocumentType } from '../../../documents/DocumentTypes'; +import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView'; import { CollectionStackingView } from '../../collections/CollectionStackingView'; import { CollectionViewType } from '../../collections/CollectionView'; import { ViewBoxAnnotatableProps } from '../../DocComponent'; @@ -44,7 +45,7 @@ export class MapBoxInfoWindow extends React.Component(Fon icon = "globe-asia"; text = "User Default"; } - noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; + noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; } else if (script === 'setFont') { const editorView = RichTextMenu.Instance?.TextView?.EditorView; text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 14d6e8be6..b08955b48 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -113,6 +113,7 @@ export class PresBox extends ViewBoxBaseComponent @computed get activeItem() { return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); } @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } @computed get scrollable(): boolean { + //TODO: likely do NOT have to update this for note-taking view, but still worth putting here if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; else return false; } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 8a5491b4b..6b71ed24f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -216,7 +216,7 @@ export class Doc extends RefField { return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); } - @computed get __LAYOUT__() { + @computed get __LAYOUT__(): any { const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); if (templateLayoutDoc) { let renderFieldKey: any; -- cgit v1.2.3-70-g09d2 From 1e044cdb3d9004a42dfce5de83f518e95aebaf40 Mon Sep 17 00:00:00 2001 From: ljungster Date: Mon, 11 Apr 2022 20:16:40 -0400 Subject: items get dragged and dropped --- .../views/collections/CollectionNoteTakingView.tsx | 80 +++++++++++++++++----- .../CollectionNoteTakingViewFieldColumn.tsx | 8 +-- src/client/views/nodes/DocumentView.tsx | 20 +++++- 3 files changed, 82 insertions(+), 26 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 1bf9a8fe4..6887c535b 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -18,7 +18,6 @@ import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; -import { EditableView } from "../EditableView"; import { LightboxView } from "../LightboxView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView"; @@ -87,6 +86,11 @@ export class CollectionNoteTakingView extends CollectionSubView; } + //TODO: this is definitely incorrect at the moment, since you completely changed getDocWidth getDocTransform(doc: Doc, dref?: DocumentView) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); @@ -370,21 +379,39 @@ export class CollectionNoteTakingView extends CollectionSubView; } - // TODO: plj + // TODO: This is where you want to create a copy of the document to take its place @action onPointerOver = (e: React.PointerEvent) => { - // console.log("hovering over something") - if (DragManager.docsBeingDragged.length) { - // essentially copying code from onInternalDrop for this: - const doc = DragManager.docsBeingDragged[0]; - // console.log(doc[LayoutSym]()) - - console.log(doc[DataSym]); - console.log(Doc.IndexOf(doc, this.childDocs)); - + if (DragManager.docsBeingDragged.length && this.childDocList) { + const clientY = e.clientY; + let dropInd = -1; + let dropAfter = 0; + this._docXfs.forEach((cd, i) => { + const pos = cd.stackedDocTransform().inverse().transformPoint(0, -2 * this.gridGap)[1]; + const pos1 = cd.stackedDocTransform().inverse().transformPoint(0, cd.height())[1]; + // checking whethere the copied element is in between the top of the doc and the grid gap + // (meaning that this is the index it will be taking) + if (clientY > pos && (clientY < pos1 || i == this._docXfs.length - 1)) { + dropInd = i; + if (clientY > (pos + pos1) / 2) { + dropAfter = 1; + } + } + }) + const docs = this.childDocList; + const newDocs = DragManager.docsBeingDragged; + if (docs) { + const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; + const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); + newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); + // doesn't appear to be causing issues, but potentially could create + // if (this.placeHolderDown) { + // docs.splice(0, 1); + // this.placeHolderDown = false + // } + docs.splice(insertInd - offset, 0, ...newDocs); + } } - - } //used in onPointerOver to swap two nodes in the rendered filtered children list @@ -398,14 +425,17 @@ export class CollectionNoteTakingView extends CollectionSubView { // Fairly confident that this is where the swapping of nodes in the various arrays happens - console.log('drop') const where = [de.x, de.y]; // start at -1 until we're sure we want to add it to the column + //Parker added this to reset doc colors + + // let dropInd = -1; let dropAfter = 0; if (de.complete.docDragData) { @@ -413,9 +443,18 @@ export class CollectionNoteTakingView extends CollectionSubView { const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); - if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) { + // const top = cd.height(); + // const pos = cd.stackedDocTransform().transformPoint(0, cd.height()); + // TODO: plan + // Get the top of the box + // Check if there could possibly be a box below + + // const pos1 = cd.stackedDocTransform().transformPoint(0, cd.height()); + // if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) { + if (where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) { dropInd = i; - const axis = this.isStackingView ? 1 : 0; + // const axis = this.isStackingView ? 1 : 0; + const axis = 1; dropAfter = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0; } }); @@ -433,6 +472,11 @@ export class CollectionNoteTakingView extends CollectionSubView this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); + // doesn't appear to be causing issues, but potentially could create + // if (this.placeHolderDown) { + // docs.splice(0, 1); + // this.placeHolderDown = false + // } docs.splice(insertInd - offset, 0, ...newDocs); } } @@ -462,7 +506,6 @@ export class CollectionNoteTakingView extends CollectionSubView => { - console.log('external drop') const where = [e.clientX, e.clientY]; let targInd = -1; this._docXfs.map((cd, i) => { @@ -635,7 +678,6 @@ export class CollectionNoteTakingView extends CollectionSubView { + console.log('in startDrag') // is MakeAlias a way to make a copy of a doc without rendering it? const alias = Doc.MakeAlias(this.props.Document); alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1); @@ -171,7 +171,9 @@ export class CollectionNoteTakingViewFieldColumn extends React.Component headings.indexOf(i) === idx); return (
void; updateFilterDoc?: (doc: Doc) => void; + // Parker added both of these + originalBackgroundColor?: string; + isNoteTakingView?: boolean; } export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView @@ -409,6 +412,10 @@ export class DocumentViewInternal extends DocComponent { + doc.backgroundColor = "#C9DAEF" + doc.opacity = 0.5 + }) const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0); dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top); dragData.dropAction = dropAction; @@ -416,9 +423,16 @@ export class DocumentViewInternal extends DocComponent (ffview.ChildDrag = this.props.DocumentView())); - DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) }, - () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed. + ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView())); + DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStar &&!this.props.isNoteTakingView)}, + () => setTimeout(action(() => { + ffview && (ffview.ChildDrag = undefined) + //TODO: is there a better way than adding another field to the props? Not quite sure how "this" works tbh + if (this.props.isNoteTakingView) { + this.props.Document.backgroundColor = ""; + this.props.Document.opacity = 1; + } + }))); // this needs to happen after the drop event is processed. ffview?.setupDragLines(false); } } -- cgit v1.2.3-70-g09d2 From 29c52a0b7582288dfb9f318c78b5b2b1a4b8894a Mon Sep 17 00:00:00 2001 From: ljungster Date: Fri, 3 Jun 2022 13:13:24 -0500 Subject: no longer changes drag color on other views --- .../views/collections/CollectionNoteTakingView.tsx | 47 ---------------------- .../CollectionNoteTakingViewFieldColumn.tsx | 5 --- src/client/views/nodes/DocumentView.tsx | 10 +++-- 3 files changed, 6 insertions(+), 56 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 1e3a27d24..bfadfd99e 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -332,26 +332,6 @@ export class CollectionNoteTakingView extends CollectionSubView { - // runInAction(() => this._cursor = "grabbing"); - // setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction); - // } - - // @action - // onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { - // this.layoutDoc._columnWidth = Math.max(10, this.columnWidth + delta[0]); - // return false; - // } - - //TODO: currently unused. How can we make use of it? - // @computed get columnDragger() { - // return
- // - //
; - // } - resizeColumns = (n: number) => { //TODO: that isn't the proper width of columns const totalWidth = this.PanelWidth @@ -492,11 +472,8 @@ export class CollectionNoteTakingView extends CollectionSubView => { const where = [e.clientX, e.clientY]; let targInd = -1; @@ -523,7 +500,6 @@ export class CollectionNoteTakingView extends CollectionSubView Array.from(this.Sections); refList: any[] = []; sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { - // const key = this.pivotField; const type = "number"; return this.refList.splice(this.refList.indexOf(ref), 1)} @@ -605,29 +581,6 @@ export class CollectionNoteTakingView extends CollectionSubView { - // const eles = HTMLElement[e.currentTarget.Elem] - // DragManager.StartDrag(e.target, {}, e.clientX, e.clientY) - // } - - // @action - // onDividerPointerOver = (e: React.PointerEvent, index: number) => { - // if (DragManager.docsBeingDragged.length == 0) { - // //convert client X to how we're doing stuff - // const xPos = e.clientX + 2 * this.xMargin - // // get difference (-25 is because that's the center of the divider) - // const xDividerPos = this.columnStartXCoords[index + 1] - 25 - // const diff = xDividerPos - xPos - // // make a copy of the array - // const colXCoords : number[] = [] - // this.columnStartXCoords.forEach(val => colXCoords.push(val)) - // colXCoords[index + 1] -= diff - // this.columnStartXCoords = colXCoords - // } - // } - @action setColumnStartXCoords = (movementX: number, colIndex: number) => { const coords = [...this.columnStartXCoords] diff --git a/src/client/views/collections/CollectionNoteTakingViewFieldColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewFieldColumn.tsx index 8b7461fc0..90196ff1c 100644 --- a/src/client/views/collections/CollectionNoteTakingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewFieldColumn.tsx @@ -7,7 +7,6 @@ import { Id } from "../../../fields/FieldSymbols"; import { RichTextField } from "../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ScriptField } from "../../../fields/ScriptField"; -import { NumCast } from "../../../fields/Types"; import { ImageField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, returnEmptyString, setupMoveUpEvents } from "../../../Utils"; @@ -136,10 +135,6 @@ export class CollectionNoteTakingViewFieldColumn extends React.Component NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); - // const heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; - // newDoc.heading = heading; FormattedTextBox.SelectOnLoad = newDoc[Id]; FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " "; return this.props.addDocument?.(newDoc) || false; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 51820e77c..c0ee77e4e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -412,10 +412,12 @@ export class DocumentViewInternal extends DocComponent { - doc.backgroundColor = "#C9DAEF" - doc.opacity = 0.5 - }) + if (this.props.isNoteTakingView) { + dragData.draggedDocuments.forEach((doc) => { + doc.backgroundColor = "#C9DAEF" + doc.opacity = 0.5 + }) + } const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0); dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top); dragData.dropAction = dropAction; -- cgit v1.2.3-70-g09d2 From 7045605675916fd3767f6adb8ba0f9e61f27f7ed Mon Sep 17 00:00:00 2001 From: ljungster Date: Tue, 14 Jun 2022 10:44:38 -0500 Subject: stashing before massive overhaul --- src/client/views/StyleProvider.tsx | 2 + .../views/collections/CollectionNoteTakingView.tsx | 73 +++++++++++++--------- src/client/views/nodes/DocumentView.tsx | 7 ++- 3 files changed, 50 insertions(+), 32 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 77aafaaff..c1a088b36 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -90,6 +90,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { - //TODO: can somebody explain me to what exactly TraceMobX is? TraceMobx(); - // appears that we are going to reset the _docXfs. TODO: what is Xfs? this._docXfs.length = 0; return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); //TODO change style here so that - const style = { width: width(), marginTop: i ? this.gridGap : 0, height: height() }; + const style = { width: width(), marginTop: this.gridGap, height: height() }; return
{this.getDisplayDoc(d, width)}
; @@ -108,7 +106,6 @@ export class CollectionNoteTakingView extends CollectionSubView(); const columnHeaders = Array.from(this.columnHeaders); const fields = new Map(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); @@ -116,10 +113,8 @@ export class CollectionNoteTakingView extends CollectionSubView { if (!d[this.pivotField]) { d[this.pivotField] = columnHeaders.length > 0 ? columnHeaders[0].heading : `New Column` - // d[this.pivotField] = columnHeaders[0].heading }; const sectionValue = d[this.pivotField] as object; - // the next five lines ensures that floating point rounding errors don't create more than one section -syip const parsed = parseInt(sectionValue.toString()); const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue; @@ -347,38 +342,57 @@ export class CollectionNoteTakingView extends CollectionSubView { if (DragManager.docsBeingDragged.length && this.childDocList) { + // get the current column based on the mouse's x coordinate + // const clientX = e.clientX - 2 * this.gridGap // unsure how large left tab is, may need to change the subtraction op + // let col = 0 + // for (let i = 0; i < this.columnStartXCoords.length; i++) { + // if (clientX > this.columnStartXCoords[i]) { + // col = i + // } + // } + const col = this.getClientColumn(e) + // get all of the docs in that column + let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]]; + if (this.pivotField) { + const entries = Array.from(this.Sections.entries()); + sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries; + } + const docs = sections[col][1] + // get the index for where you need to insert the doc you are currently dragging const clientY = e.clientY + console.log(clientY) let dropInd = -1; let dropAfter = 0; - //TODO get the nodes in the doc - this._docXfs.forEach((cd, i) => { - //TODO need to write your own function for this, or make sure you're properly updating the fields - const pos = cd.noteTakingDocTransform().inverse().transformPoint(0, -2 * this.gridGap); - const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(0, cd.height()); - // checking whethere the copied element is in between the top of the doc and the grid gap - // (meaning that this is the index it will be taking) - const yCoordInBetween = clientY > pos[1] && (clientY < pos1[1] || i == this._docXfs.length - 1) - // const xCoordInBetween = clientX > pos[0] && (clientX < pos1[0] || i == this._docXfs.length - 1) - const xCoordInBetween = true - if (yCoordInBetween && xCoordInBetween) { + docs.forEach((doc, i) => { + let dref: Opt; + const noteTakingDocTransform = () => this.getDocTransform(doc, dref); + const pos0 = noteTakingDocTransform().inverse().transformPoint(0, 0); + const pos1 = noteTakingDocTransform().inverse().transformPoint(0, this.getDocHeight(doc) + 2 * this.gridGap); + // const pos = cd.noteTakingDocTransform().inverse().transformPoint(0, 0); + // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(0, cd.height() + 2 * this.gridGap); + // updating drop position based on y coordinates + const yCoordInBetween = clientY > pos0[1] && (clientY < pos1[1] || i == docs.length - 1) + if (yCoordInBetween) { dropInd = i; - if (clientY > (pos[1] + pos1[1]) / 2) { + console.log(dropInd) + if (clientY > (pos0[1] + pos1[1]) / 2) { dropAfter = 1; } } }) - const docs = this.childDocList; + // Are we sure that we need all of this stuff? + // const docs = this.childDocList; const newDocs = DragManager.docsBeingDragged; - if (docs) { - const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; - const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); - newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); - docs.splice(insertInd - offset, 0, ...newDocs); - } + // if (docs) { + // const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; + // const insertInd = dropInd === -1 ? docs.length : dropInd; + // const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); + // newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); + docs.splice(dropInd + dropAfter, 0, ...newDocs); + // } } } @@ -526,7 +540,6 @@ export class CollectionNoteTakingView extends CollectionSubView "", SetValue: this.addGroup, @@ -588,7 +600,7 @@ export class CollectionNoteTakingView extends CollectionSubView { doc.backgroundColor = "#C9DAEF" @@ -424,6 +424,11 @@ export class DocumentViewInternal extends DocComponent (ffview.ChildDrag = this.props.DocumentView())); DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStar &&!this.props.isNoteTakingView)}, -- cgit v1.2.3-70-g09d2 From c660a8b1846db1bc05cf8f496f77e706fe469711 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 13 Jul 2022 10:48:25 -0400 Subject: fixed linkdoc preview not going away when clicking outside of it. --- src/client/views/linking/LinkMenu.tsx | 63 +++++++++++++++++-------------- src/client/views/nodes/LinkDocPreview.tsx | 16 ++++---- 2 files changed, 42 insertions(+), 37 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 17d28e886..0096a58bd 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -1,17 +1,17 @@ -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../../fields/Doc"; -import { LinkManager } from "../../util/LinkManager"; -import { DocumentView } from "../nodes/DocumentView"; -import { LinkDocPreview } from "../nodes/LinkDocPreview"; -import { LinkEditor } from "./LinkEditor"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc } from '../../../fields/Doc'; +import { LinkManager } from '../../util/LinkManager'; +import { DocumentView } from '../nodes/DocumentView'; +import { LinkDocPreview } from '../nodes/LinkDocPreview'; +import { LinkEditor } from './LinkEditor'; import './LinkMenu.scss'; -import { LinkMenuGroup } from "./LinkMenuGroup"; -import React = require("react"); +import { LinkMenuGroup } from './LinkMenuGroup'; +import React = require('react'); interface Props { docView: DocumentView; - position?: { x?: number, y?: number }; + position?: { x?: number; y?: number }; itemHandler?: (doc: Doc) => void; clearLinkEditor: () => void; } @@ -34,13 +34,16 @@ export class LinkMenu extends React.Component { this._editingLink = undefined; }); - componentDidMount() { document.addEventListener("pointerdown", this.onPointerDown); } - componentWillUnmount() { document.removeEventListener("pointerdown", this.onPointerDown); } + componentDidMount() { + document.addEventListener('pointerdown', this.onPointerDown, true); + } + componentWillUnmount() { + document.removeEventListener('pointerdown', this.onPointerDown, true); + } onPointerDown = action((e: PointerEvent) => { LinkDocPreview.Clear(); - if (!this._linkMenuRef.current?.contains(e.target as any) && - !this._editorRef.current?.contains(e.target as any)) { + if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) { this.clear(); } }); @@ -51,7 +54,7 @@ export class LinkMenu extends React.Component { * @returns list of link JSX elements if there at least one linked element */ renderAllGroups = (groups: Map>): Array => { - const linkItems = Array.from(groups.entries()).map(group => + const linkItems = Array.from(groups.entries()).map(group => ( { group={group[1]} groupType={group[0]} clearLinkEditor={this.clear} - showEditor={action(linkDoc => this._editingLink = linkDoc)} />); + showEditor={action(linkDoc => (this._editingLink = linkDoc))} + /> + )); return linkItems.length ? linkItems : this.props.position ? [<>] : [

No links have been created yet. Drag the linking button onto another document to create a link.

]; - } + }; render() { const sourceDoc = this.props.docView.props.Document; - return
- {this._editingLink ? -
- this._editingLink = undefined)} /> -
: -
- {this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))} -
} -
; + return ( +
+ {this._editingLink ? ( +
+ (this._editingLink = undefined))} /> +
+ ) : ( +
{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}
+ )} +
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index c2fb5d4ec..d8b943ae4 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -3,20 +3,20 @@ import { Tooltip } from '@material-ui/core'; 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, Cast } from '../../../fields/Types'; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils'; +import { Doc, DocCastAsync, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs, DocUtils } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DragManager } from '../../util/DragManager'; +import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { DocumentView, DocumentViewSharedProps } from './DocumentView'; import './LinkDocPreview.scss'; import React = require('react'); -import { DocumentType } from '../../documents/DocumentTypes'; -import { DragManager } from '../../util/DragManager'; -import { LinkFollower } from '../../util/LinkFollower'; interface LinkDocPreviewProps { linkDoc?: Doc; @@ -65,12 +65,12 @@ export class LinkDocPreview extends React.Component { } componentDidMount() { this.init(); - document.addEventListener('pointerdown', this.onPointerDown); + document.addEventListener('pointerdown', this.onPointerDown, true); } componentWillUnmount() { LinkDocPreview.SetLinkInfo(undefined); - document.removeEventListener('pointerdown', this.onPointerDown); + document.removeEventListener('pointerdown', this.onPointerDown, true); } onPointerDown = (e: PointerEvent) => { -- cgit v1.2.3-70-g09d2 From 3f4fa566b8e723e5b04f014107dd746c1585228b Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 13 Jul 2022 12:02:48 -0400 Subject: fixed slider size for dropdown fonticonbox --- src/client/views/nodes/button/FontIconBox.scss | 34 ++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index 6cd56f84e..a1ca777b3 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -1,4 +1,4 @@ -@import "../../global/globalCssVariables"; +@import '../../global/globalCssVariables'; .menuButton { height: 100%; @@ -31,8 +31,6 @@ text-transform: uppercase; font-weight: bold; transition: 0.15s; - - } .fontIconBox-icon { @@ -106,31 +104,31 @@ right: 0; bottom: 0; background-color: lightgrey; - -webkit-transition: .4s; - transition: .4s; + -webkit-transition: 0.4s; + transition: 0.4s; } .slider:before { position: absolute; - content: ""; + content: ''; height: 21px; width: 21px; left: 2px; bottom: 2px; background-color: $white; - -webkit-transition: .4s; - transition: .4s; + -webkit-transition: 0.4s; + transition: 0.4s; } - input:checked+.slider { + input:checked + .slider { background-color: $medium-blue; } - input:focus+.slider { + input:focus + .slider { box-shadow: 0 0 1px $medium-blue; } - input:checked+.slider:before { + input:checked + .slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); @@ -197,8 +195,6 @@ } } - - &.colorBtn, &.colorBtnLabel { color: black; @@ -311,18 +307,21 @@ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); border-radius: $standard-border-radius; - input[type=range]::-webkit-slider-runnable-track { + .menu-slider { + height: 10px; + } + input[type='range']::-webkit-slider-runnable-track { background: gray; height: 3px; } - input[type=range]::-webkit-slider-thumb { + input[type='range']::-webkit-slider-thumb { box-shadow: 1px 1px 1px #000000; border: 1px solid #000000; height: 10px; width: 10px; border-radius: 5px; - background: #FFFFFF; + background: #ffffff; cursor: pointer; -webkit-appearance: none; margin-top: -4px; @@ -456,5 +455,4 @@ background: transparent; position: fixed; } - -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From a0b032c49e792e1bcaa69a584cacacb7e950bef1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 13 Jul 2022 22:13:35 -0400 Subject: fixed fonticonbox's to use mobx correctly --- src/client/views/nodes/button/FontIconBox.tsx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 1ce97979e..5e279f984 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -86,11 +86,21 @@ export class FontIconBox extends DocComponent() { } // Determining UI Specs - @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); - @observable private icon = StrCast(this.dataDoc.icon, 'user') as any; - @observable private dropdown: boolean = BoolCast(this.rootDoc.dropDownOpen); - @observable private buttonList: string[] = StrListCast(this.rootDoc.btnList); - @observable private type = StrCast(this.rootDoc.btnType); + @computed get label() { + return StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); + } + @computed get icon() { + return StrCast(this.dataDoc.icon, 'user') as any; + } + @computed get dropdown() { + return BoolCast(this.rootDoc.dropDownOpen); + } + @computed get buttonList() { + return StrListCast(this.rootDoc.btnList); + } + @computed get type() { + return StrCast(this.rootDoc.btnType); + } /** * Types of buttons in dash: -- cgit v1.2.3-70-g09d2 From 724c1c1228aa601fdbbed93dde68a5a9fa9c5ae9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 20 Jul 2022 09:12:11 -0400 Subject: added a 'guest' login mode. added ability for mainView to be any doc, not just a docking collection. --- src/client/DocServer.ts | 4 +- src/client/util/CurrentUserUtils.ts | 18 +- src/client/util/GroupManager.tsx | 9 +- src/client/util/SettingsManager.tsx | 3 +- src/client/util/SharingManager.tsx | 3 +- src/client/util/TrackMovements.ts | 141 ++++++++------- src/client/views/ContextMenu.scss | 22 +-- src/client/views/ContextMenuItem.tsx | 84 +++++---- src/client/views/DashboardView.tsx | 2 +- src/client/views/GlobalKeyHandler.ts | 4 +- src/client/views/Main.tsx | 51 +++--- src/client/views/MainView.tsx | 53 ++++-- src/client/views/PropertiesDocContextSelector.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 70 ++++---- src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 15 +- src/client/views/nodes/MapBox/MapBox.tsx | 48 ++--- src/client/views/nodes/PDFBox.tsx | 5 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 2 +- src/client/views/topbar/TopBar.tsx | 2 +- src/fields/util.ts | 47 +++-- src/mobile/MobileInkOverlay.tsx | 119 ++++++------- src/mobile/MobileMain.tsx | 5 +- src/server/ApiManagers/UserManager.ts | 77 ++++---- src/server/RouteManager.ts | 58 +++--- src/server/authentication/AuthenticationManager.ts | 31 ++-- src/server/authentication/DashUserModel.ts | 106 ++++++----- src/server/index.ts | 108 +++++------- src/server/websocket.ts | 194 +++++++++++---------- views/login.pug | 4 +- 31 files changed, 682 insertions(+), 609 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 1b0ba6bc3..5a34fcf11 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -77,7 +77,7 @@ export namespace DocServer { } export function getFieldWriteMode(field: string) { - return fieldWriteModes[field] || WriteMode.Default; + return Doc.CurrentUserEmail === 'guest' ? WriteMode.LiveReadonly : fieldWriteModes[field] || WriteMode.Default; } export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) { @@ -178,7 +178,7 @@ export namespace DocServer { _isReadOnly = true; _CreateField = field => (_cache[field[Id]] = field); _UpdateField = emptyFunction; - _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB + // _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB } } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 6c80cf0f4..bbf2ff3f9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,6 +1,7 @@ import { reaction } from "mobx"; import * as rp from 'request-promise'; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; +import { Id } from "../../fields/FieldSymbols"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; @@ -14,6 +15,7 @@ import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents"; import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; import { TreeViewType } from "../views/collections/CollectionTreeView"; +import { DashboardView } from "../views/DashboardView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; @@ -762,8 +764,6 @@ export class CurrentUserUtils { doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true }); } - // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved. - PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); if (doc.clickFuncs === undefined) { const onClick = Docs.Create.ScriptingDocument(undefined, { @@ -789,7 +789,6 @@ export class CurrentUserUtils { }, "onCheckedClick"); doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true }); } - PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); return doc.clickFuncs as Doc; } @@ -886,13 +885,20 @@ export class CurrentUserUtils { public static async loadUserDocument(id: string) { await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids); - if (userDocumentId !== "guest") { + if (userDocumentId) { return DocServer.GetRefField(userDocumentId).then(async field => { Docs.newAccount = !(field instanceof Doc); await Docs.Prototypes.initialize(); const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; - Docs.newAccount &&(userDoc.activePage = "home"); - return this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); + this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); + if (Docs.newAccount) { + if (Doc.CurrentUserEmail === "guest") { + DashboardView.createNewDashboard(undefined, "guest dashboard"); + } else { + userDoc.activePage = "home"; + } + } + return userDoc; }); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 59334f6a2..c8b784390 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -14,6 +14,7 @@ import { GroupMemberView } from './GroupMemberView'; import { SharingManager, User } from './SharingManager'; import { listSpec } from '../../fields/Schema'; import { DateField } from '../../fields/DateField'; +import { Id } from '../../fields/FieldSymbols'; /** * Interface for options for the react-select component @@ -49,9 +50,11 @@ export class GroupManager extends React.Component<{}> { * Fetches the list of users stored on the database. */ populateUsers = async () => { - const userList = await RequestPromise.get(Utils.prepend('/getUsers')); - const raw = JSON.parse(userList) as User[]; - raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email))); + if (Doc.UserDoc()[Id] !== '__guest__') { + const userList = await RequestPromise.get(Utils.prepend('/getUsers')); + const raw = JSON.parse(userList) as User[]; + raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email))); + } }; /** diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 12d1793af..cf143c5e8 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -4,6 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorState, SketchPicker } from 'react-color'; import { Doc } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; import { BoolCast, Cast, StrCast } from '../../fields/Types'; import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; @@ -81,7 +82,7 @@ export class SettingsManager extends React.Component<{}> { if (this.playgroundMode) { DocServer.Control.makeReadOnly(); addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' }); - } else DocServer.Control.makeEditable(); + } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable(); }); @undoBatch diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 793027ea1..895bd3374 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -23,6 +23,7 @@ import { GroupMemberView } from './GroupMemberView'; import { SelectionManager } from './SelectionManager'; import './SharingManager.scss'; import { LinkManager } from './LinkManager'; +import { Id } from '../../fields/FieldSymbols'; export interface User { email: string; @@ -136,7 +137,7 @@ export class SharingManager extends React.Component<{}> { * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument. */ populateUsers = async () => { - if (!this.populating) { + if (!this.populating && Doc.UserDoc()[Id] !== '__guest__') { this.populating = true; const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = JSON.parse(userList) as User[]; diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts index d512e4802..4a2ccd706 100644 --- a/src/client/util/TrackMovements.ts +++ b/src/client/util/TrackMovements.ts @@ -1,27 +1,26 @@ -import { IReactionDisposer, observable, observe, reaction } from "mobx"; -import { NumCast } from "../../fields/Types"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import { Id } from "../../fields/FieldSymbols"; +import { IReactionDisposer, observable, observe, reaction } from 'mobx'; +import { NumCast } from '../../fields/Types'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { CollectionDockingView } from '../views/collections/CollectionDockingView'; +import { Id } from '../../fields/FieldSymbols'; export type Movement = { - time: number, - panX: number, - panY: number, - scale: number, - docId: string, -} + time: number; + panX: number; + panY: number; + scale: number; + docId: string; +}; export type Presentation = { - movements: Movement[] | null, - totalTime: number, - meta: Object | Object[], -} + movements: Movement[] | null; + totalTime: number; + meta: Object | Object[]; +}; export class TrackMovements { - private static get NULL_PRESENTATION(): Presentation { - return { movements: null, meta: {}, totalTime: -1, } + return { movements: null, meta: {}, totalTime: -1 }; } // instance variables @@ -32,16 +31,17 @@ export class TrackMovements { private recordingFFViews: Map | null; private tabChangeDisposeFunc: IReactionDisposer | null; - // create static instance and getter for global use @observable static _instance: TrackMovements; - static get Instance(): TrackMovements { return TrackMovements._instance } + static get Instance(): TrackMovements { + return TrackMovements._instance; + } constructor() { // init the global instance TrackMovements._instance = this; // init the instance variables - this.currentPresentation = TrackMovements.NULL_PRESENTATION + this.currentPresentation = TrackMovements.NULL_PRESENTATION; this.tracking = false; this.absoluteStart = -1; @@ -52,28 +52,37 @@ export class TrackMovements { // little helper :) private get nullPresentation(): boolean { - return this.currentPresentation.movements === null + return this.currentPresentation.movements === null; } private addRecordingFFView(doc: Doc, key: string = doc[Id]): void { // console.info('adding dispose func : docId', key, 'doc', doc); - if (this.recordingFFViews === null) { console.warn('addFFView on null RecordingApi'); return; } - if (this.recordingFFViews.has(key)) { console.warn('addFFView : key already in map'); return; } + if (this.recordingFFViews === null) { + console.warn('addFFView on null RecordingApi'); + return; + } + if (this.recordingFFViews.has(key)) { + console.warn('addFFView : key already in map'); + return; + } const disposeFunc = reaction( - () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0)}), - (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovement(res.x, res.y, key, res.scale), + () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0) }), + res => res.x !== -1 && res.y !== -1 && this.tracking && this.trackMovement(res.x, res.y, key, res.scale) ); this.recordingFFViews?.set(key, disposeFunc); } private removeRecordingFFView = (key: string) => { // console.info('removing dispose func : docId', key); - if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; } + if (this.recordingFFViews === null) { + console.warn('removeFFView on null RecordingApi'); + return; + } this.recordingFFViews.get(key)?.(); this.recordingFFViews.delete(key); - } + }; // in the case where only one tab was changed (updates not across dashboards), set only one to true private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => { @@ -86,7 +95,6 @@ export class TrackMovements { if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]); } - // new tab was added - need to add it if (tabbedFFViews.size > this.recordingFFViews.size) { for (const DashDoc of tabbedDocs) { @@ -103,13 +111,13 @@ export class TrackMovements { // tab was removed - need to remove it from recordingFFViews else if (tabbedFFViews.size < this.recordingFFViews.size) { for (const [key] of this.recordingFFViews) { - if (!tabbedFFViews.has(key)) { + if (!tabbedFFViews.has(key)) { this.removeRecordingFFView(key); if (onlyOne) return; - } + } } } - } + }; private initTabTracker = () => { if (this.recordingFFViews === null) { @@ -117,18 +125,19 @@ export class TrackMovements { } // init the dispose funcs on the page - const docList = DocListCast(CollectionDockingView.Instance.props.Document.data); + const docList = DocListCast(CollectionDockingView.Instance?.props.Document.data); this.updateRecordingFFViewsFromTabs(docList); // create a reaction to monitor changes in tabs - this.tabChangeDisposeFunc = - reaction(() => CollectionDockingView.Instance.props.Document.data, - (change) => { - // TODO: consider changing between dashboards - // console.info('change in tabs', change); - this.updateRecordingFFViewsFromTabs(DocListCast(change), true); - }); - } + this.tabChangeDisposeFunc = reaction( + () => CollectionDockingView.Instance?.props.Document.data, + change => { + // TODO: consider changing between dashboards + // console.info('change in tabs', change); + this.updateRecordingFFViewsFromTabs(DocListCast(change), true); + } + ); + }; start = (meta?: Object) => { this.initTabTracker(); @@ -142,12 +151,12 @@ export class TrackMovements { this.absoluteStart = startDate.getTime(); // (2) assign meta content if it exists - this.currentPresentation.meta = meta || {} + this.currentPresentation.meta = meta || {}; // (3) assign start date to currentPresenation - this.currentPresentation.movements = [] + this.currentPresentation.movements = []; // (4) set tracking true to allow trackMovements - this.tracking = true - } + this.tracking = true; + }; /* stops the video and returns the presentatation; if no presentation, returns undefined */ yieldPresentation(clearData: boolean = true): Presentation | null { @@ -157,7 +166,7 @@ export class TrackMovements { // set the previus recording view to the play view // this.playFFView = this.recordingFFView; - // ensure we add the endTime now that they are done recording + // ensure we add the endTime now that they are done recording const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; // reset the current presentation @@ -169,10 +178,10 @@ export class TrackMovements { finish = (): void => { // make is tracking false - this.tracking = false + this.tracking = false; // reset the RecordingApi instance this.clear(); - } + }; private clear = (): void => { // clear the disposeFunc if we are done (not tracking) @@ -187,44 +196,46 @@ export class TrackMovements { } // clear presenation data - this.currentPresentation = TrackMovements.NULL_PRESENTATION + this.currentPresentation = TrackMovements.NULL_PRESENTATION; // clear absoluteStart - this.absoluteStart = -1 - } + this.absoluteStart = -1; + }; removeAllRecordingFFViews = () => { - if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; } + if (this.recordingFFViews === null) { + console.warn('removeAllFFViews on null RecordingApi'); + return; + } for (const [id, disposeFunc] of this.recordingFFViews) { // console.info('calling dispose func : docId', id); disposeFunc(); this.recordingFFViews.delete(id); } - } + }; private trackMovement = (panX: number, panY: number, docId: string, scale: number = 0) => { // ensure we are recording to track if (!this.tracking) { - console.error('[recordingApi.ts] trackMovements(): tracking is false') + console.error('[recordingApi.ts] trackMovements(): tracking is false'); return; } // check to see if the presetation is init - if not, we are between segments // TODO: make this more clear - tracking should be "live tracking", not always true when the recording api being used (between start and yieldPres) - // bacuse tracking should be false inbetween segments high key + // bacuse tracking should be false inbetween segments high key if (this.nullPresentation) { - console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments') + console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments'); return; } // get the time - const time = new Date().getTime() - this.absoluteStart + const time = new Date().getTime() - this.absoluteStart; // make new movement object - const movement: Movement = { time, panX, panY, scale, docId } + const movement: Movement = { time, panX, panY, scale, docId }; // add that movement to the current presentation data's movement array - this.currentPresentation.movements && this.currentPresentation.movements.push(movement) - } - + this.currentPresentation.movements && this.currentPresentation.movements.push(movement); + }; // method that concatenates an array of presentatations into one public concatPresentations = (presentations: Presentation[]): Presentation => { @@ -233,13 +244,15 @@ export class TrackMovements { let sumTime = 0; let combinedMetas: any[] = []; - presentations.forEach((presentation) => { + presentations.forEach(presentation => { const { movements, totalTime, meta } = presentation; // update movements if they had one if (movements) { // add the summed time to the movements - const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } }); + const addedTimeMovements = movements.map(move => { + return { ...move, time: move.time + sumTime }; + }); // concat the movements already in the combined presentation with these new ones combinedMovements.push(...addedTimeMovements); } @@ -252,6 +265,6 @@ export class TrackMovements { }); // return the combined presentation with the updated total summed time - return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; - } + return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; + }; } diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index ea24dbf6d..1e6a377de 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -1,10 +1,10 @@ -@import "global/globalCssVariables"; +@import 'global/globalCssVariables'; .contextMenu-cont { position: absolute; display: flex; z-index: 100000; - box-shadow: 0px 3px 4px rgba(0,0,0,30%); + box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%); flex-direction: column; background: whitesmoke; border-radius: 3px; @@ -28,9 +28,9 @@ position: absolute; display: flex; z-index: 1000; - box-shadow: #AAAAAA .2vw .2vw .4vw; + box-shadow: #aaaaaa 0.2vw 0.2vw 0.4vw; flex-direction: column; - border: 1px solid #BBBBBBBB; + border: 1px solid #bbbbbbbb; border-radius: 15px; padding-top: 10px; padding-bottom: 10px; @@ -49,7 +49,7 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; - transition: all .1s; + transition: all 0.1s; border-style: none; // padding: 10px 0px 10px 0px; white-space: nowrap; @@ -58,7 +58,7 @@ text-transform: uppercase; padding-right: 30px; - .icon-background { + .contextMenu-item-icon-background { pointer-events: all; background-color: transparent; width: 35px; @@ -78,7 +78,7 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; - transition: all .1s; + transition: all 0.1s; border-style: none; // padding: 10px 0px 10px 0px; white-space: nowrap; @@ -89,7 +89,7 @@ } .contextMenu-item:hover { - border-width: .11px; + border-width: 0.11px; border-style: none; border-color: $medium-gray; // rgb(187, 186, 186); border-bottom-style: solid; @@ -116,8 +116,8 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; - transition: all .1s; - border-width: .11px; + transition: all 0.1s; + border-width: 0.11px; border-style: none; border-color: $medium-gray; // rgb(187, 186, 186); // padding: 10px 0px 10px 0px; @@ -152,4 +152,4 @@ padding-left: 10px; border: solid black 1px; border-radius: 5px; -} \ No newline at end of file +} diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 30073e21f..dc9c2eb6c 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -1,9 +1,9 @@ -import React = require("react"); -import { observable, action, runInAction } from "mobx"; -import { observer } from "mobx-react"; +import React = require('react'); +import { observable, action, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { UndoManager } from "../util/UndoManager"; +import { UndoManager } from '../util/UndoManager'; export interface OriginalMenuProps { description: string; @@ -37,7 +37,7 @@ export class ContextMenuItem extends React.Component) => { - if ("event" in this.props) { + if ('event' in this.props) { this.props.closeMenu?.(); let batch: UndoManager.Batch | undefined; if (this.props.undoable !== false) { @@ -46,7 +46,7 @@ export class ContextMenuItem extends React.Component this.overItem = true), ContextMenuItem.timeout); - } + this.currentTimeout = setTimeout( + action(() => (this.overItem = true)), + ContextMenuItem.timeout + ); + }; onPointerLeave = () => { if (this.currentTimeout) { @@ -73,57 +76,68 @@ export class ContextMenuItem extends React.Component this.overItem = false), ContextMenuItem.timeout); - } + this.currentTimeout = setTimeout( + action(() => (this.overItem = false)), + ContextMenuItem.timeout + ); + }; render() { - if ("event" in this.props) { + if ('event' in this.props) { return ( -
+
{this.props.icon ? ( - + ) : null} -
- {this.props.description.replace(":", "")} -
+
{this.props.description.replace(':', '')}
); - } else if ("subitems" in this.props) { - const where = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "flex-start" : this._overPosY > window.innerHeight * 2 / 3 ? "flex-end" : "center"; - const marginTop = !this.overItem ? "" : this._overPosY < window.innerHeight / 3 ? "20px" : this._overPosY > window.innerHeight * 2 / 3 ? "-20px" : ""; + } else if ('subitems' in this.props) { + const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center'; + const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : ''; // here - const submenu = !this.overItem ? (null) : -
0 ? "90%" : "20%", marginTop + marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? '90%' : '20%', + marginTop, }}> - {this._items.map(prop => )} -
; + {this._items.map(prop => ( + + ))} +
+ ); if (!(this.props as SubmenuProps).noexpand) { - return
- {this._items.map(prop => )} -
; + return ( +
+ {this._items.map(prop => ( + + ))} +
+ ); } return ( -
+
{this.props.icon ? ( - + ) : null} -
+
{this.props.description} - +
{submenu}
); } } -} \ No newline at end of file +} diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 526a32f5a..9ea9128bd 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -238,7 +238,7 @@ export class DashboardView extends React.Component { } else if (doc.readOnly) { DocServer.Control.makeReadOnly(); } else { - DocServer.Control.makeEditable(); + Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable(); } } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 73e0c9933..85f579975 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -116,8 +116,8 @@ export class KeyManager { var doDeselect = true; if (SnappingManager.GetIsDragging()) { DragManager.AbortDrag(); - } else if (CollectionDockingView.Instance.HasFullScreen) { - CollectionDockingView.Instance.CloseFullScreen(); + } else if (CollectionDockingView.Instance?.HasFullScreen) { + CollectionDockingView.Instance?.CloseFullScreen(); } else if (CollectionStackedTimeline.SelectingRegion) { CollectionStackedTimeline.SelectingRegion = undefined; doDeselect = false; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index e998f1fb9..4cb1183d0 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -5,6 +5,8 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { AssignAllExtensions } from '../../extensions/General/Extensions'; +import { Utils } from '../../Utils'; +import { DocServer } from '../DocServer'; import { Docs } from '../documents/Documents'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { LinkManager } from '../util/LinkManager'; // this must come before importing Docs and CurrentUserUtils @@ -19,31 +21,28 @@ AssignAllExtensions(); MainView.Live = window.location.search.includes('live'); window.location.search.includes('safe') && CollectionView.SetSafeMode(true); const info = await CurrentUserUtils.loadCurrentUser(); - if (info.id !== '__guest__') { - // a guest will not have an id registered - await CurrentUserUtils.loadUserDocument(info.id); - } else { - await Docs.Prototypes.initialize(); + if (info.email === 'guest') DocServer.Control.makeReadOnly(); + await CurrentUserUtils.loadUserDocument(info.id); + setTimeout(() => { + document.getElementById('root')!.addEventListener( + 'wheel', + event => { + if (event.ctrlKey) { + event.preventDefault(); + } + }, + true + ); + const startload = (document as any).startLoad; + const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000); + console.log('Loading Time = ' + loading); + const d = new Date(); + d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000); + const expires = 'expires=' + d.toUTCString(); + document.cookie = `loadtime=${loading};${expires};path=/`; new LinkManager(); - } - document.getElementById('root')!.addEventListener( - 'wheel', - event => { - if (event.ctrlKey) { - event.preventDefault(); - } - }, - true - ); - const startload = (document as any).startLoad; - const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000); - console.log('Loading Time = ' + loading); - const d = new Date(); - d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000); - const expires = 'expires=' + d.toUTCString(); - document.cookie = `loadtime=${loading};${expires};path=/`; - new LinkManager(); - new TrackMovements(); - new ReplayMovements(); - ReactDOM.render(, document.getElementById('root')); + new TrackMovements(); + new ReplayMovements(); + ReactDOM.render(, document.getElementById('root')); + }, 0); })(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b61cd3409..7e032af5e 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons'; import * as far from '@fortawesome/free-regular-svg-icons'; import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, observable, reaction } from 'mobx'; +import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; @@ -77,12 +77,15 @@ export class MainView extends React.Component { @observable private _panelContent: string = 'none'; @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel; @observable private _leftMenuFlyoutWidth: number = 0; + @computed get _hideUI() { + return this.mainDoc && this.mainDoc._viewType !== CollectionViewType.Docking; + } @computed private get dashboardTabHeight() { - return 27; + return this._hideUI ? 0 : 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @computed private get topOfDashUI() { - return Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', '')); + return this._hideUI ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', '')); } @computed private get topOfHeaderBarDoc() { return this.topOfDashUI; @@ -105,7 +108,12 @@ export class MainView extends React.Component { @computed private get colorScheme() { return StrCast(Doc.ActiveDashboard?.colorScheme); } + @observable mainDoc: Opt; @computed private get mainContainer() { + if (window.location.pathname.startsWith('/doc/')) { + DocServer.GetRefField(window.location.pathname.substring('/doc/'.length)).then(main => runInAction(() => (this.mainDoc = main as Doc))); + return this.mainDoc; + } return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard; } @computed private get headerBarDoc() { @@ -116,10 +124,10 @@ export class MainView extends React.Component { } headerBarDocWidth = () => this.mainDocViewWidth(); - headerBarDocHeight = () => SettingsManager.headerBarHeight ?? 0; - topMenuHeight = () => 35; + headerBarDocHeight = () => (this._hideUI ? 0 : SettingsManager.headerBarHeight ?? 0); + topMenuHeight = () => (this._hideUI ? 0 : 35); topMenuWidth = returnZero; // value is ignored ... - leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace('px', '')); + leftMenuWidth = () => (this._hideUI ? 0 : Number(LEFT_MENU_WIDTH.replace('px', ''))); leftMenuHeight = () => this._dashUIHeight; leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth; leftMenuFlyoutHeight = () => this._dashUIHeight; @@ -212,7 +220,8 @@ export class MainView extends React.Component { const pathname = window.location.pathname.substr(1).split('/'); if (pathname.length > 1 && pathname[0] === 'doc') { Doc.MainDocId = pathname[1]; - !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field))); + //!this.userDoc && + DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field))); } } @@ -455,7 +464,11 @@ export class MainView extends React.Component { AudioBox.Enabled = true; const targets = document.elementsFromPoint(e.x, e.y); if (targets.length) { - const targClass = targets[0].className.toString(); + let targClass = targets[0].className.toString(); + for (let i = 0; i < targets.length - 1; i++) { + if (typeof targets[i].className === 'object') targClass = targets[i + 1].className.toString(); + else break; + } !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu(); !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu(); } @@ -517,7 +530,7 @@ export class MainView extends React.Component { return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined); } headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1); - + mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1); @computed get headerBarDocView() { return (
@@ -557,7 +570,7 @@ export class MainView extends React.Component { @computed get mainDocView() { return ( <> - {this.headerBarDocView} + {this._hideUI ? null : this.headerBarDocView} ); @@ -750,7 +763,7 @@ export class MainView extends React.Component { const width = this.propertiesWidth() + leftMenuFlyoutWidth; return ( <> - {this.leftMenuPanel} + {this._hideUI ? null : this.leftMenuPanel}
{this.flyout}
@@ -759,9 +772,11 @@ export class MainView extends React.Component {
{this.dockingContent} -
- -
+ {this._hideUI ? null : ( +
+ +
+ )}
{this.propertiesWidth() < 10 ? null : }
@@ -962,7 +977,7 @@ export class MainView extends React.Component { - + {this._hideUI ? null : } {LinkDescriptionPopup.descriptionPopup ? : null} {DocumentLinksButton.LinkEditorDocView ? (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null} {LinkDocPreview.LinkInfo ? : null} @@ -973,7 +988,7 @@ export class MainView extends React.Component { default: return ( <> -
+
{this.mainDashboardArea} diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 0f63ebc1d..9d89ee036 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -40,7 +40,7 @@ export class PropertiesDocContextSelector extends React.Component !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)) + .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance?.props.Document)) .filter(doc => !Doc.IsSystem(doc)) .map(doc => ({ col: doc, target })); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 42f9bb981..ed9054107 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -29,7 +29,7 @@ const _global = (window /* browser */ || global) /* node */ as any; @observer export class CollectionDockingView extends CollectionSubView() { - @observable public static Instance: CollectionDockingView; + @observable public static Instance: CollectionDockingView | undefined; public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) { return { type: 'react-component', @@ -103,12 +103,14 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch public static CloseSplit(document: Opt, panelName?: string): boolean { - const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document)); - if (tab) { - const j = tab.header.parent.contentItems.indexOf(tab.contentItem); - if (j !== -1) { - tab.header.parent.contentItems[j].remove(); - return CollectionDockingView.Instance.layoutChanged(); + if (CollectionDockingView.Instance) { + const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => (panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document)); + if (tab) { + const j = tab.header.parent.contentItems.indexOf(tab.contentItem); + if (j !== -1) { + tab.header.parent.contentItems[j].remove(); + return CollectionDockingView.Instance.layoutChanged(); + } } } @@ -119,19 +121,21 @@ export class CollectionDockingView extends CollectionSubView() { public static OpenFullScreen(doc: Doc) { SelectionManager.DeselectAll(); const instance = CollectionDockingView.Instance; - if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') { - return DashboardView.openDashboard(doc); + if (instance) { + if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') { + return DashboardView.openDashboard(doc); + } + const newItemStackConfig = { + type: 'stack', + content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))], + }; + const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); + instance._goldenLayout.root.contentItems[0].addChild(docconfig); + docconfig.callDownwards('_$init'); + instance._goldenLayout._$maximiseItem(docconfig); + instance._goldenLayout.emit('stateChanged'); + instance.stateChanged(); } - const newItemStackConfig = { - type: 'stack', - content: [CollectionDockingView.makeDocumentConfig(Doc.MakeAlias(doc))], - }; - const docconfig = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); - instance._goldenLayout.root.contentItems[0].addChild(docconfig); - docconfig.callDownwards('_$init'); - instance._goldenLayout._$maximiseItem(docconfig); - instance._goldenLayout.emit('stateChanged'); - instance.stateChanged(); return true; } @@ -146,21 +150,23 @@ export class CollectionDockingView extends CollectionSubView() { const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout); stack.addChild(newContentItem.contentItems[0], undefined); stack.contentItems[activeContentItemIndex].remove(); - return CollectionDockingView.Instance.layoutChanged(); + return instance.layoutChanged(); } - const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName); + const tab = Array.from(instance.tabMap.keys()).find(tab => tab.contentItem.config.props.panelName === panelName); if (tab) { tab.header.parent.addChild(newConfig, undefined); const j = tab.header.parent.contentItems.indexOf(tab.contentItem); !addToSplit && j !== -1 && tab.header.parent.contentItems[j].remove(); - return CollectionDockingView.Instance.layoutChanged(); + return instance.layoutChanged(); } return CollectionDockingView.AddSplit(document, panelName, stack, panelName); } @undoBatch public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) { - return Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) : CollectionDockingView.AddSplit(doc, location, stack, panelName); + return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 + ? CollectionDockingView.CloseSplit(doc) + : CollectionDockingView.AddSplit(doc, location, stack, panelName); } // @@ -170,7 +176,7 @@ export class CollectionDockingView extends CollectionSubView() { @action public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) { if (document._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document); - + if (!CollectionDockingView.Instance) return false; const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); if (tab) { tab.header.parent.setActiveContentItem(tab.contentItem); @@ -453,13 +459,15 @@ export class CollectionDockingView extends CollectionSubView() { Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); } - const dview = CollectionDockingView.Instance.props.Document; - const fieldKey = CollectionDockingView.Instance.props.fieldKey; - Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); - this.tabMap.delete(tab); - tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); - this.stateChanged(); + if (CollectionDockingView.Instance) { + const dview = CollectionDockingView.Instance.props.Document; + const fieldKey = CollectionDockingView.Instance.props.fieldKey; + Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); + this.tabMap.delete(tab); + tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); + tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); + this.stateChanged(); + } }; tabCreated = (tab: any) => { this.tabMap.add(tab); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2ab5f6247..f0cb23eab 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -225,7 +225,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent + DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data).forEach(childClick => onClicks.push({ description: `Set child ${childClick.title}`, icon: 'edit', diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index b8aaea622..2d08b1c09 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -302,6 +302,7 @@ export class TabDocView extends React.Component { pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array }); if ( + CollectionDockingView.Instance && !Array.from(CollectionDockingView.Instance.tabMap) .map(d => d.DashDoc) .includes(curPres) @@ -420,7 +421,7 @@ export class TabDocView extends React.Component { ScreenToLocalTransform = () => { this._forceInvalidateScreenToLocal; const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); - return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY); + return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY) ?? Transform.Identity(); }; PanelWidth = () => this._panelWidth; PanelHeight = () => this._panelHeight; @@ -449,9 +450,9 @@ export class TabDocView extends React.Component { PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} styleProvider={DefaultStyleProvider} - docFilters={CollectionDockingView.Instance.childDocFilters} - docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters} - searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} + docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} + docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} + searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} addDocument={undefined} removeDocument={this.remDocTab} addDocTab={this.addDocTab} @@ -624,9 +625,9 @@ export class TabMinimapView extends React.Component { styleProvider={TabMinimapView.miniStyleProvider} addDocTab={this.props.addDocTab} pinToPres={TabDocView.PinDoc} - docFilters={CollectionDockingView.Instance.childDocFilters} - docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters} - searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} + docFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyDoclist} + docRangeFilters={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist} + searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} fitContentsToBox={returnTrue} />
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index bf4c029b2..c4b42301e 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api'; -import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc'; @@ -12,6 +12,7 @@ import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Uti import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; +import { UndoManager } from '../../../util/UndoManager'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; @@ -368,23 +369,27 @@ export class MapBox extends ViewBoxAnnotatableComponent { - const localDelta = this.props - .ScreenToLocalTransform() - .scale(this.props.scaling?.() || 1) - .transformDirection(delta[0], delta[1]); - const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); - const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; - if (ratio >= 1) { - this.layoutDoc.nativeWidth = nativeWidth * ratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; - this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; - } - return false; - }, + (e, down, delta) => + runInAction(() => { + const localDelta = this.props + .ScreenToLocalTransform() + .scale(this.props.scaling?.() || 1) + .transformDirection(delta[0], delta[1]); + const fullWidth = this.layoutDoc[WidthSym](); + const mapWidth = fullWidth - this.sidebarWidth(); + if (this.sidebarWidth() + localDelta[0] > 0) { + this._showSidebar = true; + this.layoutDoc._width = fullWidth + localDelta[0]; + this.layoutDoc._sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%'; + } else { + this._showSidebar = false; + this.layoutDoc._width = mapWidth; + this.layoutDoc._sidebarWidthPercent = '0%'; + } + return false; + }), emptyFunction, - this.toggleSidebar + () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map') ); }; @@ -470,13 +475,14 @@ export class MapBox extends ViewBoxAnnotatableComponent { + //1.2 * w * ? = .2 * w .2/1.2 const prevWidth = this.sidebarWidth(); - this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; - this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); + this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%'; + this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); }; sidebarDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); + setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true); }; sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this._ref.current!.getBoundingClientRect(); @@ -605,7 +611,7 @@ export class MapBox extends ViewBoxAnnotatableComponent - + e.stopPropagation()} placeholder="Enter location" /> {this.renderMarkers()} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 5b9d90780..f41f6a1ad 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -13,20 +13,19 @@ import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { KeyCodes } from '../../util/KeyCodes'; import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { Colors } from '../global/globalEnums'; import { CreateImage } from '../nodes/WebBoxRenderer'; -import { AnchorMenu } from '../pdf/AnchorMenu'; import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; -import './PDFBox.scss'; import { VideoBox } from './VideoBox'; +import './PDFBox.scss'; import React = require('react'); -import { CollectionFreeFormView } from '../collections/collectionFreeForm'; @observer export class PDFBox extends ViewBoxAnnotatableComponent() { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index cfaa428f9..e945920da 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -640,7 +640,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); + setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true); }; sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this._ref.current!.getBoundingClientRect(); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 292c187e4..6e5eb3300 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -399,7 +399,7 @@ export class PresBox extends ViewBoxBaseComponent() { const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined; const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc); - const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext); + const tab = CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext); this.turnOffEdit(); // Handles the setting of presCollection if (includesDoc) { diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 0bf1575fb..3fc0a237e 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -26,7 +26,7 @@ import './TopBar.scss'; @observer export class TopBar extends React.Component { navigateToHome = () => { - CollectionDockingView.Instance.CaptureThumbnail()?.then(() => { + CollectionDockingView.Instance?.CaptureThumbnail()?.then(() => { Doc.ActivePage = 'home'; DashboardView.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use }); diff --git a/src/fields/util.ts b/src/fields/util.ts index cbb560114..41e723119 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -1,41 +1,40 @@ +import { action, observable, runInAction, trace } from 'mobx'; +import { computedFn } from 'mobx-utils'; +import { DocServer } from '../client/DocServer'; +import { SerializationHelper } from '../client/util/SerializationHelper'; import { UndoManager } from '../client/util/UndoManager'; +import { returnZero } from '../Utils'; +import CursorField from './CursorField'; import { - Doc, - FieldResult, - UpdatingFromServer, - LayoutSym, - AclPrivate, + AclAdmin, + AclAugment, AclEdit, + AclPrivate, AclReadonly, - AclAugment, + AclSelfEdit, AclSym, + AclUnset, DataSym, + Doc, DocListCast, - AclAdmin, - HeightSym, - WidthSym, - updateCachedAcls, - AclUnset, DocListCastAsync, + FieldResult, ForceServerWrite, + HeightSym, Initializing, - AclSelfEdit, + LayoutSym, + updateCachedAcls, + UpdatingFromServer, + WidthSym, } from './Doc'; -import { SerializationHelper } from '../client/util/SerializationHelper'; -import { ProxyField, PrefetchProxy } from './Proxy'; -import { RefField } from './RefField'; +import { Id, OnUpdate, Parent, Self, SelfProxy, Update } from './FieldSymbols'; +import { List } from './List'; import { ObjectField } from './ObjectField'; -import { action, observable, runInAction, trace } from 'mobx'; -import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from './FieldSymbols'; -import { DocServer } from '../client/DocServer'; +import { PrefetchProxy, ProxyField } from './Proxy'; +import { RefField } from './RefField'; +import { RichTextField } from './RichTextField'; import { ComputedField } from './ScriptField'; import { ScriptCast, StrCast } from './Types'; -import { returnZero } from '../Utils'; -import CursorField from './CursorField'; -import { List } from './List'; -import { SnappingManager } from '../client/util/SnappingManager'; -import { computedFn } from 'mobx-utils'; -import { RichTextField } from './RichTextField'; function _readOnlySetter(): never { throw new Error("Documents can't be modified in read-only mode"); diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx index d668d134e..6415099fd 100644 --- a/src/mobile/MobileInkOverlay.tsx +++ b/src/mobile/MobileInkOverlay.tsx @@ -1,13 +1,12 @@ import React = require('react'); -import { observer } from "mobx-react"; -import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message"; -import { observable, action } from "mobx"; -import { GestureUtils } from "../pen-gestures/GestureUtils"; -import "./MobileInkOverlay.scss"; -import { DragManager } from "../client/util/DragManager"; +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; import { DocServer } from '../client/DocServer'; +import { DragManager } from '../client/util/DragManager'; import { Doc } from '../fields/Doc'; - +import { GestureUtils } from '../pen-gestures/GestureUtils'; +import { GestureContent, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent } from '../server/Message'; +import './MobileInkOverlay.scss'; @observer export default class MobileInkOverlay extends React.Component { @@ -18,7 +17,7 @@ export default class MobileInkOverlay extends React.Component { @observable private _height: number = 0; @observable private _x: number = -300; @observable private _y: number = -300; - @observable private _text: string = ""; + @observable private _text: string = ''; @observable private _offsetX: number = 0; @observable private _offsetY: number = 0; @@ -49,7 +48,7 @@ export default class MobileInkOverlay extends React.Component { this._scale = scaledSize.scale; this._x = 300; // TODO: center on screen this._y = 25; // TODO: center on screen - this._text = text ? text : ""; + this._text = text ? text : ''; } @action @@ -64,31 +63,29 @@ export default class MobileInkOverlay extends React.Component { // TODO: figure out why strokes drawn in corner of mobile interface dont get inserted const { points, bounds } = content; - console.log("received points", points, bounds); + console.log('received points', points, bounds); const B = { - right: (bounds.right * this._scale) + this._x, - left: (bounds.left * this._scale) + this._x, // TODO: scale - bottom: (bounds.bottom * this._scale) + this._y, - top: (bounds.top * this._scale) + this._y, // TODO: scale + right: bounds.right * this._scale + this._x, + left: bounds.left * this._scale + this._x, // TODO: scale + bottom: bounds.bottom * this._scale + this._y, + top: bounds.top * this._scale + this._y, // TODO: scale width: bounds.width * this._scale, height: bounds.height * this._scale, }; const target = document.elementFromPoint(this._x + 10, this._y + 10); target?.dispatchEvent( - new CustomEvent("dashOnGesture", - { - bubbles: true, - detail: { - points: points, - gesture: GestureUtils.Gestures.Stroke, - bounds: B - } - } - ) + new CustomEvent('dashOnGesture', { + bubbles: true, + detail: { + points: points, + gesture: GestureUtils.Gestures.Stroke, + bounds: B, + }, + }) ); - } + }; uploadDocument = async (content: MobileDocumentUploadContent) => { const { docId } = content; @@ -100,36 +97,34 @@ export default class MobileInkOverlay extends React.Component { const complete = new DragManager.DragCompleteEvent(false, dragData); if (target) { - console.log("dispatching upload doc!!!!", target, doc); + console.log('dispatching upload doc!!!!', target, doc); target.dispatchEvent( - new CustomEvent("dashOnDrop", - { - bubbles: true, - detail: { - x: this._x, - y: this._y, - complete: complete, - altKey: false, - metaKey: false, - ctrlKey: false, - shiftKey: false, - embedKey: false - } - } - ) + new CustomEvent('dashOnDrop', { + bubbles: true, + detail: { + x: this._x, + y: this._y, + complete: complete, + altKey: false, + metaKey: false, + ctrlKey: false, + shiftKey: false, + embedKey: false, + }, + }) ); } else { - alert("TARGET IS UNDEFINED"); + alert('TARGET IS UNDEFINED'); } } - } + }; @action dragStart = (e: React.PointerEvent) => { - document.removeEventListener("pointermove", this.dragging); - document.removeEventListener("pointerup", this.dragEnd); - document.addEventListener("pointermove", this.dragging); - document.addEventListener("pointerup", this.dragEnd); + document.removeEventListener('pointermove', this.dragging); + document.removeEventListener('pointerup', this.dragEnd); + document.addEventListener('pointermove', this.dragging); + document.addEventListener('pointerup', this.dragEnd); this._isDragging = true; this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left; @@ -137,7 +132,7 @@ export default class MobileInkOverlay extends React.Component { e.preventDefault(); e.stopPropagation(); - } + }; @action dragging = (e: PointerEvent) => { @@ -150,41 +145,39 @@ export default class MobileInkOverlay extends React.Component { e.preventDefault(); e.stopPropagation(); - } + }; @action dragEnd = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.dragging); - document.removeEventListener("pointerup", this.dragEnd); + document.removeEventListener('pointermove', this.dragging); + document.removeEventListener('pointerup', this.dragEnd); this._isDragging = false; e.preventDefault(); e.stopPropagation(); - } + }; render() { - return ( -
+ pointerEvents: 'none', + borderStyle: this._isDragging ? 'solid' : 'dashed', + }} + ref={this._mainCont}>

{this._text}

-
+
); } -} \ No newline at end of file +} diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx index f85f05f53..6cbf86f77 100644 --- a/src/mobile/MobileMain.tsx +++ b/src/mobile/MobileMain.tsx @@ -12,10 +12,7 @@ AssignAllExtensions(); const info = await CurrentUserUtils.loadCurrentUser(); DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + ' (mobile)'); await Docs.Prototypes.initialize(); - if (info.id !== '__guest__') { - // a guest will not have an id registered - await CurrentUserUtils.loadUserDocument(info.id); - } + await CurrentUserUtils.loadUserDocument(info.id); document.getElementById('root')!.addEventListener( 'wheel', event => { diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts index 7be8a1e9f..53e55c1c3 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -1,10 +1,10 @@ -import ApiManager, { Registration } from "./ApiManager"; -import { Method } from "../RouteManager"; -import { Database } from "../database"; -import { msToTime } from "../ActionUtilities"; -import * as bcrypt from "bcrypt-nodejs"; -import { Opt } from "../../fields/Doc"; -import { WebSocket } from "../websocket"; +import ApiManager, { Registration } from './ApiManager'; +import { Method } from '../RouteManager'; +import { Database } from '../database'; +import { msToTime } from '../ActionUtilities'; +import * as bcrypt from 'bcrypt-nodejs'; +import { Opt } from '../../fields/Doc'; +import { WebSocket } from '../websocket'; export const timeMap: { [id: string]: number } = {}; interface ActivityUnit { @@ -13,28 +13,26 @@ interface ActivityUnit { } export default class UserManager extends ApiManager { - protected initialize(register: Registration): void { - register({ method: Method.GET, - subscription: "/getUsers", + subscription: '/getUsers', secureHandler: async ({ res }) => { - const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, "users"); + const cursor = await Database.Instance.query({}, { email: 1, linkDatabaseId: 1, sharingDocumentId: 1 }, 'users'); const results = await cursor.toArray(); res.send(results.map((user: any) => ({ email: user.email, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }))); - } + }, }); register({ method: Method.POST, - subscription: "/setCacheDocumentIds", + subscription: '/setCacheDocumentIds', secureHandler: async ({ user, req, res }) => { const result: any = {}; user.cacheDocumentIds = req.body.cacheDocumentIds; user.save(err => { if (err) { - result.error = [{ msg: "Error while caching documents" }]; + result.error = [{ msg: 'Error while caching documents' }]; } }); @@ -42,32 +40,35 @@ export default class UserManager extends ApiManager { // console.log(e); // }); res.send(result); - } + }, }); register({ method: Method.GET, - subscription: "/getUserDocumentIds", - secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }) + subscription: '/getUserDocumentIds', + secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, linkDatabaseId: user.linkDatabaseId, sharingDocumentId: user.sharingDocumentId }), + publicHandler: ({ res }) => res.send({ userDocumentId: '__guest__', linkDatabaseId: 3, sharingDocumentId: 2 }), }); register({ method: Method.GET, - subscription: "/getSharingDocumentId", - secureHandler: ({ res, user }) => res.send(user.sharingDocumentId) + subscription: '/getSharingDocumentId', + secureHandler: ({ res, user }) => res.send(user.sharingDocumentId), + publicHandler: ({ res }) => res.send(2), }); register({ method: Method.GET, - subscription: "/getLinkDatabaseId", - secureHandler: ({ res, user }) => res.send(user.linkDatabaseId) + subscription: '/getLinkDatabaseId', + secureHandler: ({ res, user }) => res.send(user.linkDatabaseId), + publicHandler: ({ res }) => res.send(3), }); register({ method: Method.GET, - subscription: "/getCurrentUser", + subscription: '/getCurrentUser', secureHandler: ({ res, user: { _id, email, cacheDocumentIds } }) => res.send(JSON.stringify({ id: _id, email, cacheDocumentIds })), - publicHandler: ({ res }) => res.send(JSON.stringify({ id: "__guest__", email: "" })) + publicHandler: ({ res }) => res.send(JSON.stringify({ id: '__guest__', email: 'guest' })), }); register({ @@ -80,7 +81,7 @@ export default class UserManager extends ApiManager { const validated = await new Promise>(resolve => { bcrypt.compare(curr_pass, user.password, (err, passwords_match) => { if (err || !passwords_match) { - result.error = [{ msg: "Incorrect current password" }]; + result.error = [{ msg: 'Incorrect current password' }]; res.send(result); resolve(undefined); } else { @@ -93,10 +94,10 @@ export default class UserManager extends ApiManager { return; } - req.assert("new_pass", "Password must be at least 4 characters long").len({ min: 4 }); - req.assert("new_confirm", "Passwords do not match").equals(new_pass); + req.assert('new_pass', 'Password must be at least 4 characters long').len({ min: 4 }); + req.assert('new_confirm', 'Passwords do not match').equals(new_pass); if (curr_pass === new_pass) { - result.error = [{ msg: "Current and new password are the same" }]; + result.error = [{ msg: 'Current and new password are the same' }]; } // was there error in validating new passwords? if (req.validationErrors()) { @@ -113,17 +114,17 @@ export default class UserManager extends ApiManager { user.save(err => { if (err) { - result.error = [{ msg: "Error while saving new password" }]; + result.error = [{ msg: 'Error while saving new password' }]; } }); res.send(result); - } + }, }); register({ method: Method.GET, - subscription: "/activity", + subscription: '/activity', secureHandler: ({ res }) => { const now = Date.now(); @@ -135,25 +136,23 @@ export default class UserManager extends ApiManager { const socketPair = Array.from(WebSocket.socketMap).find(pair => pair[1] === user); if (socketPair && !socketPair[0].disconnected) { const duration = now - time; - const target = (duration / 1000) < (60 * 5) ? activeTimes : inactiveTimes; + const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes; target.push({ user, duration }); } } - const process = (target: { user: string, duration: number }[]) => { + const process = (target: { user: string; duration: number }[]) => { const comparator = (first: ActivityUnit, second: ActivityUnit) => first.duration - second.duration; const sorted = target.sort(comparator); return sorted.map(({ user, duration }) => `${user} (${msToTime(duration)})`); }; - res.render("user_activity.pug", { - title: "User Activity", + res.render('user_activity.pug', { + title: 'User Activity', active: process(activeTimes), - inactive: process(inactiveTimes) + inactive: process(inactiveTimes), }); - } + }, }); - } - -} \ No newline at end of file +} diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index aa9bfcfa7..5683cd539 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -1,12 +1,12 @@ import { cyan, green, red } from 'colors'; import { Express, Request, Response } from 'express'; -import { AdminPriviliges } from "."; -import { DashUserModel } from "./authentication/DashUserModel"; -import RouteSubscriber from "./RouteSubscriber"; +import { AdminPriviliges } from '.'; +import { DashUserModel } from './authentication/DashUserModel'; +import RouteSubscriber from './RouteSubscriber'; export enum Method { GET, - POST + POST, } export interface CoreArguments { @@ -33,13 +33,13 @@ const registered = new Map>(); enum RegistrationError { Malformed, - Duplicate + Duplicate, } export default class RouteManager { private server: Express; private _isRelease: boolean; - private failedRegistrations: { route: string, reason: RegistrationError }[] = []; + private failedRegistrations: { route: string; reason: RegistrationError }[] = []; public get isRelease() { return this._isRelease; @@ -74,39 +74,42 @@ export default class RouteManager { } process.exit(1); } else { - console.log(green("all server routes have been successfully registered:")); - Array.from(registered.keys()).sort().forEach(route => console.log(cyan(route))); + console.log(green('all server routes have been successfully registered:')); + Array.from(registered.keys()) + .sort() + .forEach(route => console.log(cyan(route))); console.log(); } - } + }; static routes: string[] = []; /** - * - * @param initializer + * + * @param initializer */ addSupervisedRoute = (initializer: RouteInitializer): void => { const { method, subscription, secureHandler, publicHandler, errorHandler, requireAdminInRelease: requireAdmin } = initializer; - typeof (initializer.subscription) === "string" && RouteManager.routes.push(initializer.subscription); + typeof initializer.subscription === 'string' && RouteManager.routes.push(initializer.subscription); initializer.subscription instanceof RouteSubscriber && RouteManager.routes.push(initializer.subscription.root); - initializer.subscription instanceof Array && initializer.subscription.map(sub => { - typeof (sub) === "string" && RouteManager.routes.push(sub); - sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root); - }); + initializer.subscription instanceof Array && + initializer.subscription.map(sub => { + typeof sub === 'string' && RouteManager.routes.push(sub); + sub instanceof RouteSubscriber && RouteManager.routes.push(sub.root); + }); const isRelease = this._isRelease; const supervised = async (req: Request, res: Response) => { let user = req.user as Partial | undefined; const { originalUrl: target } = req; - if (process.env.DB === "MEM" && !user) { - user = { id: "guest", email: "", userDocumentId: "guestDocId" }; + if (process.env.DB === 'MEM' && !user) { + user = { id: 'guest', email: 'guest', userDocumentId: '__guest__' }; } const core = { req, res, isRelease }; const tryExecute = async (toExecute: (args: any) => any | Promise, args: any) => { try { await toExecute(args); } catch (e) { - console.log(red(target), user && ("email" in user) ? "" : undefined); + console.log(red(target), user && 'email' in user ? '' : undefined); if (errorHandler) { errorHandler({ ...core, error: e }); } else { @@ -119,7 +122,7 @@ export default class RouteManager { if (AdminPriviliges.get(user.id)) { AdminPriviliges.delete(user.id); } else { - return res.redirect(`/admin/${req.originalUrl.substring(1).replace("/", ":")}`); + return res.redirect(`/admin/${req.originalUrl.substring(1).replace('/', ':')}`); } } await tryExecute(secureHandler, { ...core, user }); @@ -128,10 +131,10 @@ export default class RouteManager { if (publicHandler) { await tryExecute(publicHandler, core); if (!res.headersSent) { - res.redirect("/login"); + // res.redirect("/login"); } } else { - res.redirect("/login"); + res.redirect('/login'); } } setTimeout(() => { @@ -144,7 +147,7 @@ export default class RouteManager { }; const subscribe = (subscriber: RouteSubscriber | string) => { let route: string; - if (typeof subscriber === "string") { + if (typeof subscriber === 'string') { route = subscriber; } else { route = subscriber.build; @@ -152,7 +155,7 @@ export default class RouteManager { if (!/^\/$|^\/[A-Za-z\*]+(\/\:[A-Za-z?_\*]+)*$/g.test(route)) { this.failedRegistrations.push({ reason: RegistrationError.Malformed, - route + route, }); } else { const existing = registered.get(route); @@ -160,7 +163,7 @@ export default class RouteManager { if (existing.has(method)) { this.failedRegistrations.push({ reason: RegistrationError.Duplicate, - route + route, }); return; } @@ -184,15 +187,14 @@ export default class RouteManager { } else { subscribe(subscription); } - } - + }; } export const STATUS = { OK: 200, BAD_REQUEST: 400, EXECUTION_ERROR: 500, - PERMISSION_DENIED: 403 + PERMISSION_DENIED: 403, }; export function _error(res: Response, message: string, error?: any) { diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts index 3622be4c5..52d876e95 100644 --- a/src/server/authentication/AuthenticationManager.ts +++ b/src/server/authentication/AuthenticationManager.ts @@ -1,4 +1,4 @@ -import { default as User, DashUserModel } from './DashUserModel'; +import { default as User, DashUserModel, initializeGuest } from './DashUserModel'; import { Request, Response, NextFunction } from 'express'; import * as passport from 'passport'; import { IVerifyOptions } from 'passport-local'; @@ -30,6 +30,7 @@ export let getSignup = (req: Request, res: Response) => { * Create a new local account. */ export let postSignup = (req: Request, res: Response, next: NextFunction) => { + const email = req.body.email as String; req.assert('email', 'Email is not valid').isEmail(); req.assert('password', 'Password must be at least 4 characters long').len({ min: 4 }); req.assert('confirmPassword', 'Passwords do not match').equals(req.body.password); @@ -41,15 +42,14 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { return res.redirect('/signup'); } - const email = req.body.email as String; const password = req.body.password; const model = { email, password, - userDocumentId: Utils.GenerateGuid(), - sharingDocumentId: Utils.GenerateGuid(), - linkDatabaseId: Utils.GenerateGuid(), + userDocumentId: email === 'guest' ? '__guest__' : Utils.GenerateGuid(), + sharingDocumentId: email === 'guest' ? 2 : Utils.GenerateGuid(), + linkDatabaseId: email === 'guest' ? 3 : Utils.GenerateGuid(), cacheDocumentIds: '', } as Partial; @@ -106,18 +106,22 @@ export let getLogin = (req: Request, res: Response) => { * On failure, redirect to signup page */ export let postLogin = (req: Request, res: Response, next: NextFunction) => { - req.assert('email', 'Email is not valid').isEmail(); - req.assert('password', 'Password cannot be blank').notEmpty(); - req.sanitize('email').normalizeEmail({ gmail_remove_dots: false }); - - const errors = req.validationErrors(); + if (req.body.email === '') { + User.findOne({ email: 'guest' }, (err: any, user: DashUserModel) => !user && initializeGuest()); + req.body.email = 'guest'; + req.body.password = 'guest'; + } else { + req.assert('email', 'Email is not valid').isEmail(); + req.assert('password', 'Password cannot be blank').notEmpty(); + req.sanitize('email').normalizeEmail({ gmail_remove_dots: false }); + } - if (errors) { + if (req.validationErrors()) { req.flash('errors', 'Unable to login at this time. Please try again.'); return res.redirect('/signup'); } - passport.authenticate('local', (err: Error, user: DashUserModel, _info: IVerifyOptions) => { + const callback = (err: Error, user: DashUserModel, _info: IVerifyOptions) => { if (err) { next(err); return; @@ -132,7 +136,8 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => { } tryRedirectToTarget(req, res); }); - })(req, res, next); + }; + setTimeout(() => passport.authenticate('local', callback)(req, res, next), 500); }; /** diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts index bee28b96d..a1883beab 100644 --- a/src/server/authentication/DashUserModel.ts +++ b/src/server/authentication/DashUserModel.ts @@ -1,13 +1,13 @@ //@ts-ignore -import * as bcrypt from "bcrypt-nodejs"; +import * as bcrypt from 'bcrypt-nodejs'; //@ts-ignore import * as mongoose from 'mongoose'; export type DashUserModel = mongoose.Document & { - email: String, - password: string, - passwordResetToken?: string, - passwordResetExpires?: Date, + email: String; + password: string; + passwordResetToken?: string; + passwordResetExpires?: Date; userDocumentId: string; sharingDocumentId: string; @@ -15,66 +15,74 @@ export type DashUserModel = mongoose.Document & { cacheDocumentIds: string; profile: { - name: string, - gender: string, - location: string, - website: string, - picture: string - }, + name: string; + gender: string; + location: string; + website: string; + picture: string; + }; - comparePassword: comparePasswordFunction, + comparePassword: comparePasswordFunction; }; type comparePasswordFunction = (candidatePassword: string, cb: (err: any, isMatch: any) => {}) => void; export type AuthToken = { - accessToken: string, - kind: string + accessToken: string; + kind: string; }; -const userSchema = new mongoose.Schema({ - email: String, - password: String, - passwordResetToken: String, - passwordResetExpires: Date, +const userSchema = new mongoose.Schema( + { + email: String, + password: String, + passwordResetToken: String, + passwordResetExpires: Date, - userDocumentId: String, // id that identifies a document which hosts all of a user's account data - sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users - linkDatabaseId: String, - cacheDocumentIds: String, // set of document ids to retreive on startup + userDocumentId: String, // id that identifies a document which hosts all of a user's account data + sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users + linkDatabaseId: String, + cacheDocumentIds: String, // set of document ids to retreive on startup - facebook: String, - twitter: String, - google: String, + facebook: String, + twitter: String, + google: String, - profile: { - name: String, - gender: String, - location: String, - website: String, - picture: String - } -}, { timestamps: true }); + profile: { + name: String, + gender: String, + location: String, + website: String, + picture: String, + }, + }, + { timestamps: true } +); /** * Password hash middleware. */ -userSchema.pre("save", function save(next) { +userSchema.pre('save', function save(next) { const user = this as DashUserModel; - if (!user.isModified("password")) { + if (!user.isModified('password')) { return next(); } bcrypt.genSalt(10, (err: any, salt: string) => { if (err) { return next(err); } - bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash: string) => { - if (err) { - return next(err); + bcrypt.hash( + user.password, + salt, + () => void {}, + (err: mongoose.Error, hash: string) => { + if (err) { + return next(err); + } + user.password = hash; + next(); } - user.password = hash; - next(); - }); + ); }); }); @@ -88,5 +96,15 @@ const comparePassword: comparePasswordFunction = function (this: DashUserModel, userSchema.methods.comparePassword = comparePassword; -const User = mongoose.model("User", userSchema); -export default User; \ No newline at end of file +const User = mongoose.model('User', userSchema); +export function initializeGuest() { + new User({ + email: 'guest', + password: 'guest', + userDocumentId: '__guest__', + sharingDocumentId: '2', + linkDatabaseId: '3', + cacheDocumentIds: '', + }).save(); +} +export default User; diff --git a/src/server/index.ts b/src/server/index.ts index f8c32103b..6e6bde3cb 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,35 +1,35 @@ require('dotenv').config(); -import { yellow } from "colors"; +import { yellow } from 'colors'; import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; import * as qs from 'query-string'; -import { log_execution } from "./ActionUtilities"; -import DeleteManager from "./ApiManagers/DeleteManager"; +import { log_execution } from './ActionUtilities'; +import DeleteManager from './ApiManagers/DeleteManager'; import DownloadManager from './ApiManagers/DownloadManager'; -import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; -import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; -import PDFManager from "./ApiManagers/PDFManager"; +import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager'; +import GooglePhotosManager from './ApiManagers/GooglePhotosManager'; +import PDFManager from './ApiManagers/PDFManager'; import { SearchManager } from './ApiManagers/SearchManager'; -import SessionManager from "./ApiManagers/SessionManager"; -import UploadManager from "./ApiManagers/UploadManager"; +import SessionManager from './ApiManagers/SessionManager'; +import UploadManager from './ApiManagers/UploadManager'; import UserManager from './ApiManagers/UserManager'; import UtilManager from './ApiManagers/UtilManager'; import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; -import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils"; -import { DashSessionAgent } from "./DashSession/DashSessionAgent"; -import { AppliedSessionAgent } from "./DashSession/Session/agents/applied_session_agent"; +import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; +import { DashSessionAgent } from './DashSession/DashSessionAgent'; +import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent'; import { DashUploadUtils } from './DashUploadUtils'; import { Database } from './database'; -import { Logger } from "./ProcessFactory"; +import { Logger } from './ProcessFactory'; import RouteManager, { Method, PublicHandler } from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; import initializeServer, { resolvedPorts } from './server_Initialization'; export const AdminPriviliges: Map = new Map(); -export const onWindows = process.platform === "win32"; +export const onWindows = process.platform === 'win32'; export let sessionAgent: AppliedSessionAgent; -export const publicDirectory = path.resolve(__dirname, "public"); -export const filesDirectory = path.resolve(publicDirectory, "files"); +export const publicDirectory = path.resolve(__dirname, 'public'); +export const filesDirectory = path.resolve(publicDirectory, 'files'); /** * These are the functions run before the server starts @@ -43,11 +43,11 @@ async function preliminaryFunctions() { await GoogleCredentialsLoader.loadCredentials(); SSL.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); - if (process.env.DB !== "MEM") { + if (process.env.DB !== 'MEM') { await log_execution({ - startMessage: "attempting to initialize mongodb connection", - endMessage: "connection outcome determined", - action: Database.tryInitializeConnection + startMessage: 'attempting to initialize mongodb connection', + endMessage: 'connection outcome determined', + action: Database.tryInitializeConnection, }); } } @@ -56,27 +56,16 @@ async function preliminaryFunctions() { * Either clustered together as an API manager * or individually referenced below, by the completion * of this function's execution, all routes will - * be registered on the server + * be registered on the server * @param router the instance of the route manager * that will manage the registration of new routes * with the server */ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: RouteManager) { - const managers = [ - new SessionManager(), - new UserManager(), - new UploadManager(), - new DownloadManager(), - new SearchManager(), - new PDFManager(), - new DeleteManager(), - new UtilManager(), - new GeneralGoogleManager(), - new GooglePhotosManager(), - ]; + const managers = [new SessionManager(), new UserManager(), new UploadManager(), new DownloadManager(), new SearchManager(), new PDFManager(), new DeleteManager(), new UtilManager(), new GeneralGoogleManager(), new GooglePhotosManager()]; // initialize API Managers - console.log(yellow("\nregistering server routes...")); + console.log(yellow('\nregistering server routes...')); managers.forEach(manager => manager.register(addSupervisedRoute)); /** @@ -84,88 +73,87 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: */ addSupervisedRoute({ method: Method.GET, - subscription: "/", - secureHandler: ({ res }) => res.redirect("/home") + subscription: '/', + secureHandler: ({ res }) => res.redirect('/home'), }); - addSupervisedRoute({ method: Method.GET, - subscription: "/serverHeartbeat", - secureHandler: ({ res }) => res.send(true) + subscription: '/serverHeartbeat', + secureHandler: ({ res }) => res.send(true), }); addSupervisedRoute({ method: Method.GET, - subscription: "/resolvedPorts", - secureHandler: ({ res }) => res.send(resolvedPorts) + subscription: '/resolvedPorts', + secureHandler: ({ res }) => res.send(resolvedPorts), + publicHandler: ({ res }) => res.send(resolvedPorts), }); const serve: PublicHandler = ({ req, res }) => { - const detector = new mobileDetect(req.headers['user-agent'] || ""); + const detector = new mobileDetect(req.headers['user-agent'] || ''); const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html'; res.sendFile(path.join(__dirname, '../../deploy/' + filename)); }; /** - * Serves a simple password input box for any + * Serves a simple password input box for any */ addSupervisedRoute({ method: Method.GET, - subscription: new RouteSubscriber("admin").add("previous_target"), + subscription: new RouteSubscriber('admin').add('previous_target'), secureHandler: ({ res, isRelease }) => { const { PASSWORD } = process.env; if (!(isRelease && PASSWORD)) { - return res.redirect("/home"); + return res.redirect('/home'); } - res.render("admin.pug", { title: "Enter Administrator Password" }); - } + res.render('admin.pug', { title: 'Enter Administrator Password' }); + }, }); addSupervisedRoute({ method: Method.POST, - subscription: new RouteSubscriber("admin").add("previous_target"), + subscription: new RouteSubscriber('admin').add('previous_target'), secureHandler: async ({ req, res, isRelease, user: { id } }) => { const { PASSWORD } = process.env; if (!(isRelease && PASSWORD)) { - return res.redirect("/home"); + return res.redirect('/home'); } const { password } = req.body; const { previous_target } = req.params; let redirect: string; if (password === PASSWORD) { AdminPriviliges.set(id, true); - redirect = `/${previous_target.replace(":", "/")}`; + redirect = `/${previous_target.replace(':', '/')}`; } else { redirect = `/admin/${previous_target}`; } res.redirect(redirect); - } + }, }); addSupervisedRoute({ method: Method.GET, - subscription: ["/home", new RouteSubscriber("doc").add("docId")], + subscription: ['/home', new RouteSubscriber('doc').add('docId')], secureHandler: serve, publicHandler: ({ req, res, ...remaining }) => { const { originalUrl: target } = req; - const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === "true"; - const docAccess = target.startsWith("/doc/"); + const sharing = qs.parse(qs.extract(req.originalUrl), { sort: false }).sharing === 'true'; + const docAccess = target.startsWith('/doc/'); // since this is the public handler, there's no meaning of '/home' to speak of // since there's no user logged in, so the only viable operation // for a guest is to look at a shared document - if (sharing && docAccess) { + if (docAccess) { serve({ req, res, ...remaining }); } else { - res.redirect("/login"); + res.redirect('/login'); } - } + }, }); logRegistrationOutcome(); } - /** * This function can be used in two different ways. If not in release mode, * this is simply the logic that is invoked to start the server. In release mode, @@ -174,9 +162,9 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: */ export async function launchServer() { await log_execution({ - startMessage: "\nstarting execution of preliminary functions", - endMessage: "completed preliminary functions\n", - action: preliminaryFunctions + startMessage: '\nstarting execution of preliminary functions', + endMessage: 'completed preliminary functions\n', + action: preliminaryFunctions, }); await initializeServer(routeSetter); } diff --git a/src/server/websocket.ts b/src/server/websocket.ts index 1b7f5919f..9b91a35a6 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -1,24 +1,24 @@ -import * as express from "express"; -import { blue, green } from "colors"; -import { createServer, Server } from "https"; -import { networkInterfaces } from "os"; +import { blue } from 'colors'; +import * as express from 'express'; +import { createServer, Server } from 'https'; +import { networkInterfaces } from 'os'; import * as sio from 'socket.io'; -import { Socket } from "socket.io"; -import { Utils } from "../Utils"; +import { Socket } from 'socket.io'; +import { Opt } from '../fields/Doc'; +import { Utils } from '../Utils'; import { logPort } from './ActionUtilities'; -import { timeMap } from "./ApiManagers/UserManager"; -import { GoogleCredentialsLoader, SSL } from "./apis/google/CredentialsLoader"; -import YoutubeApi from "./apis/youtube/youtubeApiSample"; -import { Client } from "./Client"; -import { Database } from "./database"; -import { DocumentsCollection } from "./IDatabase"; -import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from "./Message"; -import { Search } from "./Search"; +import { timeMap } from './ApiManagers/UserManager'; +import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; +import YoutubeApi from './apis/youtube/youtubeApiSample'; +import { initializeGuest } from './authentication/DashUserModel'; +import { Client } from './Client'; +import { Database } from './database'; +import { DocumentsCollection } from './IDatabase'; +import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from './Message'; +import { Search } from './Search'; import { resolvedPorts } from './server_Initialization'; -import { Opt } from "../fields/Doc"; export namespace WebSocket { - export let _socket: Socket; const clients: { [key: string]: Client } = {}; export const socketMap = new Map(); @@ -32,14 +32,14 @@ export namespace WebSocket { resolvedPorts.socket = Number(socketPort); } let socketEndpoint: Opt; - await new Promise(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve)); + await new Promise(resolve => (socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve))); io = sio(socketEndpoint!, SSL.Credentials as any); } else { io = sio().listen(resolvedPorts.socket); } - logPort("websocket", resolvedPorts.socket); + logPort('websocket', resolvedPorts.socket); - io.on("connection", function (socket: Socket) { + io.on('connection', function (socket: Socket) { _socket = socket; socket.use((_packet, next) => { const userEmail = socketMap.get(socket); @@ -70,14 +70,14 @@ export namespace WebSocket { socket.join(room); console.log('Client ID ' + socket.id + ' created room ' + room); socket.emit('created', room, socket.id); - } else if (numClients === 1) { console.log('Client ID ' + socket.id + ' joined room ' + room); socket.in(room).emit('join', room); socket.join(room); socket.emit('joined', room, socket.id); socket.in(room).emit('ready'); - } else { // max two clients + } else { + // max two clients socket.emit('full', room); } }); @@ -97,10 +97,10 @@ export namespace WebSocket { console.log('received bye'); }); - Utils.Emit(socket, MessageStore.Foo, "handshooken"); + Utils.Emit(socket, MessageStore.Foo, 'handshooken'); Utils.AddServerHandler(socket, MessageStore.Bar, guid => barReceived(socket, guid)); - Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args)); + Utils.AddServerHandler(socket, MessageStore.SetField, args => setField(socket, args)); Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField); Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields); if (isRelease) { @@ -126,26 +126,26 @@ export namespace WebSocket { */ disconnect = () => { - socket.broadcast.emit("connection_terminated", Date.now()); + socket.broadcast.emit('connection_terminated', Date.now()); socket.disconnect(true); }; }); } function processGesturePoints(socket: Socket, content: GestureContent) { - socket.broadcast.emit("receiveGesturePoints", content); + socket.broadcast.emit('receiveGesturePoints', content); } function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) { - socket.broadcast.emit("receiveOverlayTrigger", content); + socket.broadcast.emit('receiveOverlayTrigger', content); } function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) { - socket.broadcast.emit("receiveUpdateOverlayPosition", content); + socket.broadcast.emit('receiveUpdateOverlayPosition', content); } function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) { - socket.broadcast.emit("receiveMobileDocumentUpload", content); + socket.broadcast.emit('receiveMobileDocumentUpload', content); } function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any[]) => void]) { @@ -165,27 +165,22 @@ export namespace WebSocket { const target: string[] = []; onlyFields && target.push(DocumentsCollection); await Database.Instance.dropSchema(...target); - if (process.env.DISABLE_SEARCH !== "true") { + if (process.env.DISABLE_SEARCH !== 'true') { await Search.clear(); } + initializeGuest(); } function barReceived(socket: SocketIO.Socket, userEmail: string) { clients[userEmail] = new Client(userEmail.toString()); const currentdate = new Date(); - const datetime = currentdate.getDate() + "/" - + (currentdate.getMonth() + 1) + "/" - + currentdate.getFullYear() + " @ " - + currentdate.getHours() + ":" - + currentdate.getMinutes() + ":" - + currentdate.getSeconds(); + const datetime = currentdate.getDate() + '/' + (currentdate.getMonth() + 1) + '/' + currentdate.getFullYear() + ' @ ' + currentdate.getHours() + ':' + currentdate.getMinutes() + ':' + currentdate.getSeconds(); console.log(blue(`user ${userEmail} has connected to the web socket at: ${datetime}`)); - socketMap.set(socket, userEmail + " at " + datetime); + socketMap.set(socket, userEmail + ' at ' + datetime); } function getField([id, callback]: [string, (result?: Transferable) => void]) { - Database.Instance.getDocument(id, (result?: Transferable) => - callback(result ? result : undefined)); + Database.Instance.getDocument(id, (result?: Transferable) => callback(result ? result : undefined)); } function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) { @@ -193,9 +188,9 @@ export namespace WebSocket { } function setField(socket: Socket, newValue: Transferable) { - Database.Instance.update(newValue.id, newValue, () => - socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients - if (newValue.type === Types.Text) { // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR + Database.Instance.update(newValue.id, newValue, () => socket.broadcast.emit(MessageStore.SetField.Message, newValue)); // broadcast set value to all other clients + if (newValue.type === Types.Text) { + // if the newValue has sring type, then it's suitable for searching -- pass it to SOLR Search.updateDocument({ id: newValue.id, data: { set: (newValue as any).data } }); } } @@ -213,34 +208,36 @@ export namespace WebSocket { Database.Instance.getDocuments(ids, callback); } - const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = { - "number": "_n", - "string": "_t", - "boolean": "_b", - "image": ["_t", "url"], - "video": ["_t", "url"], - "pdf": ["_t", "url"], - "audio": ["_t", "url"], - "web": ["_t", "url"], - "map": ["_t", "url"], - "script": ["_t", value => value.script.originalScript], - "RichTextField": ["_t", value => value.Text], - "date": ["_d", value => new Date(value.date).toISOString()], - "proxy": ["_i", "fieldId"], - "list": ["_l", list => { - const results = []; - for (const value of list.fields) { - const term = ToSearchTerm(value); - if (term) { - results.push(term.value); + const suffixMap: { [type: string]: string | [string, string | ((json: any) => any)] } = { + number: '_n', + string: '_t', + boolean: '_b', + image: ['_t', 'url'], + video: ['_t', 'url'], + pdf: ['_t', 'url'], + audio: ['_t', 'url'], + web: ['_t', 'url'], + map: ['_t', 'url'], + script: ['_t', value => value.script.originalScript], + RichTextField: ['_t', value => value.Text], + date: ['_d', value => new Date(value.date).toISOString()], + proxy: ['_i', 'fieldId'], + list: [ + '_l', + list => { + const results = []; + for (const value of list.fields) { + const term = ToSearchTerm(value); + if (term) { + results.push(term.value); + } } - } - return results.length ? results : null; - }] + return results.length ? results : null; + }, + ], }; - function ToSearchTerm(val: any): { suffix: string, value: any } | undefined { - + function ToSearchTerm(val: any): { suffix: string; value: any } | undefined { if (val === null || val === undefined) { return; } @@ -252,69 +249,79 @@ export namespace WebSocket { } if (Array.isArray(suffix)) { const accessor = suffix[1]; - if (typeof accessor === "function") { + if (typeof accessor === 'function') { val = accessor(val); } else { val = val[accessor]; - } suffix = suffix[0]; - } return { suffix, value: val }; } function getSuffix(value: string | [string, any]): string { - return typeof value === "string" ? value : value[0]; + return typeof value === 'string' ? value : value[0]; } function addToListField(socket: Socket, diff: Diff, curListItems?: Transferable): void { - diff.diff.$set = diff.diff.$addToSet; delete diff.diff.$addToSet;// convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones + diff.diff.$set = diff.diff.$addToSet; + delete diff.diff.$addToSet; // convert add to set to a query of the current fields, and then a set of the composition of the new fields with the old ones const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; const newListItems = diff.diff.$set[updatefield]?.fields; if (!newListItems) { - console.log("Error: addToListField - no new list items"); + console.log('Error: addToListField - no new list items'); return; } - const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((item: any) => item !== undefined) || []; - diff.diff.$set[updatefield].fields = [...curList, ...newListItems];//, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))]; + const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((item: any) => item !== undefined) || []; + diff.diff.$set[updatefield].fields = [...curList, ...newListItems]; //, ...newListItems.filter((newItem: any) => newItem === null || !curList.some((curItem: any) => curItem.fieldId ? curItem.fieldId === newItem.fieldId : curItem.heading ? curItem.heading === newItem.heading : curItem === newItem))]; const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; delete diff.diff.length; - Database.Instance.update(diff.id, diff.diff, + Database.Instance.update( + diff.id, + diff.diff, () => { if (sendBack) { - console.log("Warning: list modified during update. Composite list is being returned."); + console.log('Warning: list modified during update. Composite list is being returned.'); const id = socket.id; - socket.id = ""; + socket.id = ''; socket.broadcast.emit(MessageStore.UpdateField.Message, diff); socket.id = id; } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff); dispatchNextOp(diff.id); - }, false); + }, + false + ); } function remFromListField(socket: Socket, diff: Diff, curListItems?: Transferable): void { - diff.diff.$set = diff.diff.$remFromSet; delete diff.diff.$remFromSet; + diff.diff.$set = diff.diff.$remFromSet; + delete diff.diff.$remFromSet; const updatefield = Array.from(Object.keys(diff.diff.$set))[0]; const remListItems = diff.diff.$set[updatefield].fields; - const curList = (curListItems as any)?.fields?.[updatefield.replace("fields.", "")]?.fields.filter((f: any) => f !== null) || []; - diff.diff.$set[updatefield].fields = curList?.filter((curItem: any) => !remListItems.some((remItem: any) => remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)); + const curList = (curListItems as any)?.fields?.[updatefield.replace('fields.', '')]?.fields.filter((f: any) => f !== null) || []; + diff.diff.$set[updatefield].fields = curList?.filter( + (curItem: any) => !remListItems.some((remItem: any) => (remItem.fieldId ? remItem.fieldId === curItem.fieldId : remItem.heading ? remItem.heading === curItem.heading : remItem === curItem)) + ); const sendBack = diff.diff.length !== diff.diff.$set[updatefield].fields.length; delete diff.diff.length; - Database.Instance.update(diff.id, diff.diff, + Database.Instance.update( + diff.id, + diff.diff, () => { if (sendBack) { - console.log("SEND BACK"); + console.log('SEND BACK'); const id = socket.id; - socket.id = ""; + socket.id = ''; socket.broadcast.emit(MessageStore.UpdateField.Message, diff); socket.id = id; } else socket.broadcast.emit(MessageStore.UpdateField.Message, diff); dispatchNextOp(diff.id); - }, false); + }, + false + ); } - const pendingOps = new Map(); + const pendingOps = new Map(); function dispatchNextOp(id: string) { const next = pendingOps.get(id)!.shift(); @@ -341,7 +348,7 @@ export namespace WebSocket { function UpdateField(socket: Socket, diff: Diff) { if (CurUser !== socketMap.get(socket)) { CurUser = socketMap.get(socket); - console.log("Switch User: " + CurUser); + console.log('Switch User: ' + CurUser); } if (pendingOps.has(diff.id)) { pendingOps.get(diff.id)!.push({ diff, socket }); @@ -357,24 +364,25 @@ export namespace WebSocket { return GetRefFieldLocal([diff.id, (result?: Transferable) => SetField(socket, diff, result)]); } function SetField(socket: Socket, diff: Diff, curListItems?: Transferable) { - Database.Instance.update(diff.id, diff.diff, - () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false); + Database.Instance.update(diff.id, diff.diff, () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false); const docfield = diff.diff.$set || diff.diff.$unset; if (docfield) { const update: any = { id: diff.id }; let dynfield = false; for (let key in docfield) { - if (!key.startsWith("fields.")) continue; + if (!key.startsWith('fields.')) continue; dynfield = true; const val = docfield[key]; key = key.substring(7); - Object.values(suffixMap).forEach(suf => { update[key + getSuffix(suf)] = { set: null }; }); + Object.values(suffixMap).forEach(suf => { + update[key + getSuffix(suf)] = { set: null }; + }); const term = ToSearchTerm(val); if (term !== undefined) { const { suffix, value } = term; update[key + suffix] = { set: value }; if (key.endsWith('lastModified')) { - update["lastModified" + suffix] = value; + update['lastModified' + suffix] = value; } } } @@ -403,6 +411,4 @@ export namespace WebSocket { function CreateField(newValue: any) { Database.Instance.insert(newValue); } - } - diff --git a/views/login.pug b/views/login.pug index 98816e9c8..71c6933be 100644 --- a/views/login.pug +++ b/views/login.pug @@ -15,10 +15,10 @@ block content h3.auth_header Log In .form-group .col-sm-7 - input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus, required) + input.form-control(type='email', name='email', id='email', placeholder='Email (or blank for "guest")', autofocus) .form-group .col-sm-7 - input.form-control(type='password', name='password', id='password', placeholder='Password', required) + input.form-control(type='password', name='password', id='password', placeholder='Password') .form-group .col-sm-offset-3.col-sm-7 button.btn.btn-success(id='submit', type='submit') -- cgit v1.2.3-70-g09d2 From 8f0752368e4c5831b909050940dff42171d51ae7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 21 Jul 2022 13:28:25 -0400 Subject: fixed undo for opening/closing sidebar for pdf/web/text. fixed lightboxview for pdf's with smaller heights. --- src/client/documents/Documents.ts | 2 +- src/client/views/DocumentDecorations.tsx | 4 +++- src/client/views/nodes/DocumentView.tsx | 4 +++- src/client/views/nodes/PDFBox.tsx | 7 +++++-- src/client/views/nodes/WebBox.tsx | 13 ++++++++++--- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 13 ++++++++++++- 6 files changed, 34 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ed4c99d70..51a9283f4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -465,7 +465,7 @@ export namespace Docs { DocumentType.AUDIO, { layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, backgroundColor: 'lightGray', forceReflow: true, nativeDimModifiable: true, links: '@links(self)' }, + options: { _height: 100, backgroundColor: 'lightGray', _fitWidth: true, forceReflow: true, nativeDimModifiable: true, links: '@links(self)' }, }, ], [ diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index c55daca3f..780dcfb6d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -494,7 +494,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (fixedAspect) { if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) { if (dragRight && modifyNativeDim) { - doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc); + if (Doc.NativeWidth(doc)) { + doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc); + } } else { if (!doc._fitWidth) { actualdH = (nheight / nwidth) * actualdW; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index dea718a0d..3a8552325 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1492,7 +1492,9 @@ export class DocumentView extends React.Component { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } @computed get Yshift() { - return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && !this.layoutDoc.nativeHeightUnfrozen ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2) : 0; + return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && (!this.layoutDoc.nativeHeightUnfrozen || (!this.fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight())) + ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2) + : 0; } @computed get centeringX() { return this.props.dontCenter?.includes('x') ? 0 : this.Xshift; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index f41f6a1ad..fe18abba9 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -302,8 +302,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent batch.end(), - () => this.toggleSidebar() + (e, movement, isClick) => !isClick && batch.end(), + () => { + this.toggleSidebar(); + batch.end(); + } ); }; @observable _previewNativeWidth: Opt = undefined; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index d97277c2b..05da8bd7b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -15,7 +15,7 @@ import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupM import { Docs, DocUtils } from '../../documents/Documents'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; -import { undoBatch } from '../../util/UndoManager'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../ContextMenu'; @@ -708,6 +708,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { + const batch = UndoManager.StartBatch('sidebar'); // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf setupMoveUpEvents( this, @@ -730,8 +731,14 @@ export class WebBox extends ViewBoxAnnotatableComponent (this._draggingSidebar = false)), - () => this.toggleSidebar() + action((e, movement, isClick) => { + this._draggingSidebar = false; + !isClick && batch.end(); + }), + () => { + this.toggleSidebar(); + batch.end(); + } ); }; @observable _previewNativeWidth: Opt = undefined; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e945920da..383c22ba8 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -640,7 +640,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true); + const batch = UndoManager.StartBatch('sidebar'); + setupMoveUpEvents( + this, + e, + this.sidebarMove, + (e, movement, isClick) => !isClick && batch.end(), + () => { + this.toggleSidebar(); + batch.end(); + }, + true + ); }; sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this._ref.current!.getBoundingClientRect(); -- cgit v1.2.3-70-g09d2 From 1a9f46e50551b618c7555e8e15e6d8e1394b0fe1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 21 Jul 2022 13:36:27 -0400 Subject: fixed dragging preselements by updating dragManger pointerup --- src/client/util/DragManager.ts | 323 ++++++++++++----------- src/client/views/nodes/trails/PresElementBox.tsx | 6 +- 2 files changed, 166 insertions(+), 163 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 09b463c2f..4338072df 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,41 +1,35 @@ -import { action } from "mobx"; -import { DateField } from "../../fields/DateField"; -import { Doc, Field, Opt } from "../../fields/Doc"; -import { List } from "../../fields/List"; -import { PrefetchProxy } from "../../fields/Proxy"; -import { listSpec } from "../../fields/Schema"; -import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; -import { ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types"; -import { emptyFunction, Utils } from "../../Utils"; -import { Docs, DocUtils } from "../documents/Documents"; -import * as globalCssVariables from "../views/global/globalCssVariables.scss"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { SnappingManager } from "./SnappingManager"; -import { UndoManager } from "./UndoManager"; - -export type dropActionType = "alias" | "copy" | "move" | "same" | "proto" | "none" | undefined; // undefined = move, "same" = move but don't call removeDropProperties +import { action } from 'mobx'; +import { DateField } from '../../fields/DateField'; +import { Doc, Field, Opt } from '../../fields/Doc'; +import { List } from '../../fields/List'; +import { PrefetchProxy } from '../../fields/Proxy'; +import { listSpec } from '../../fields/Schema'; +import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; +import { ScriptField } from '../../fields/ScriptField'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { emptyFunction, Utils } from '../../Utils'; +import { Docs, DocUtils } from '../documents/Documents'; +import * as globalCssVariables from '../views/global/globalCssVariables.scss'; +import { DocumentView } from '../views/nodes/DocumentView'; +import { SnappingManager } from './SnappingManager'; +import { UndoManager } from './UndoManager'; + +export type dropActionType = 'alias' | 'copy' | 'move' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call removeDropProperties /** * Initialize drag - * @param _reference: The HTMLElement that is being dragged + * @param _reference: The HTMLElement that is being dragged * @param docFunc: The Dash document being moved * @param moveFunc: The function called when the document is moved * @param dropAction: What to do with the document when it is dropped * @param dragStarted: Method to call when the drag is started */ -export function SetupDrag( - _reference: React.RefObject, - docFunc: () => Doc | Promise | undefined, - moveFunc?: DragManager.MoveFunction, - dropAction?: dropActionType, - dragStarted?: () => void -) { +export function SetupDrag(_reference: React.RefObject, docFunc: () => Doc | Promise | undefined, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, dragStarted?: () => void) { const onRowMove = async (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); - document.removeEventListener("pointermove", onRowMove); + document.removeEventListener('pointermove', onRowMove); document.removeEventListener('pointerup', onRowUp); const doc = await docFunc(); if (doc) { @@ -47,7 +41,7 @@ export function SetupDrag( } }; const onRowUp = (): void => { - document.removeEventListener("pointermove", onRowMove); + document.removeEventListener('pointermove', onRowMove); document.removeEventListener('pointerup', onRowUp); }; const onItemDown = async (e: React.PointerEvent) => { @@ -58,8 +52,8 @@ export function SetupDrag( const dragDoc = await docFunc(); dragDoc && DragManager.StartWindowDrag?.(e, [dragDoc]); } else { - document.addEventListener("pointermove", onRowMove); - document.addEventListener("pointerup", onRowUp); + document.addEventListener('pointermove', onRowMove); + document.addEventListener('pointerup', onRowUp); } } }; @@ -69,15 +63,19 @@ export function SetupDrag( export namespace DragManager { let dragDiv: HTMLDivElement; let dragLabel: HTMLDivElement; - export let StartWindowDrag: Opt<((e: { pageX: number, pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void)>; + export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void>; export let CompleteWindowDrag: Opt<(aborted: boolean) => void>; - export function GetRaiseWhenDragged() { return BoolCast(Doc.UserDoc()._raiseWhenDragged); } - export function SetRaiseWhenDragged(val:boolean) { Doc.UserDoc()._raiseWhenDragged = val } + export function GetRaiseWhenDragged() { + return BoolCast(Doc.UserDoc()._raiseWhenDragged); + } + export function SetRaiseWhenDragged(val: boolean) { + Doc.UserDoc()._raiseWhenDragged = val; + } export function Root() { - const root = document.getElementById("root"); + const root = document.getElementById('root'); if (!root) { - throw new Error("No root element found"); + throw new Error('No root element found'); } return root; } @@ -85,27 +83,20 @@ export namespace DragManager { export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; export type RemoveFunction = (document: Doc | Doc[]) => boolean; - export interface DragDropDisposer { (): void; } + export interface DragDropDisposer { + (): void; + } export interface DragOptions { dragComplete?: (e: DragCompleteEvent) => void; // function to invoke when drag has completed - hideSource?: boolean; // hide source document during drag - offsetX?: number; // offset of top left of source drag visual from cursor + hideSource?: boolean; // hide source document during drag + offsetX?: number; // offset of top left of source drag visual from cursor offsetY?: number; noAutoscroll?: boolean; } // event called when the drag operation results in a drop action export class DropEvent { - constructor( - readonly x: number, - readonly y: number, - readonly complete: DragCompleteEvent, - readonly shiftKey: boolean, - readonly altKey: boolean, - readonly metaKey: boolean, - readonly ctrlKey: boolean, - readonly embedKey: boolean, - ) { } + constructor(readonly x: number, readonly y: number, readonly complete: DragCompleteEvent, readonly shiftKey: boolean, readonly altKey: boolean, readonly metaKey: boolean, readonly ctrlKey: boolean, readonly embedKey: boolean) {} } // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated @@ -137,20 +128,22 @@ export namespace DragManager { treeViewDoc?: Doc; offset: number[]; canEmbed?: boolean; - userDropAction: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys - defaultDropAction?: dropActionType; // an optionally specified default drop action when there is no user drop actionl - this will be honored if there is no user drop action - dropAction: dropActionType; // a drop action request by the initiating code. the actual drop action may be different -- eg, if the request is 'alias', but the document is dropped within the same collection, the drop action will be switched to 'move' + userDropAction: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys + defaultDropAction?: dropActionType; // an optionally specified default drop action when there is no user drop actionl - this will be honored if there is no user drop action + dropAction: dropActionType; // a drop action request by the initiating code. the actual drop action may be different -- eg, if the request is 'alias', but the document is dropped within the same collection, the drop action will be switched to 'move' removeDropProperties?: string[]; moveDocument?: MoveFunction; removeDocument?: RemoveFunction; isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts } export class LinkDragData { - constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc,) { + constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc) { this.linkDragView = dragView; this.linkSourceGetAnchor = linkSourceGetAnchor; } - get dragDocument() { return this.linkDragView.props.Document; } + get dragDocument() { + return this.linkDragView.props.Document; + } linkSourceGetAnchor: () => Doc; linkSourceDoc?: Doc; linkDragView: DocumentView; @@ -177,43 +170,29 @@ export namespace DragManager { userDropAction: dropActionType; } - export function MakeDropTarget( - element: HTMLElement, - dropFunc: (e: Event, de: DropEvent) => void, - doc?: Doc, - preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void, - ): DragDropDisposer { - if ("canDrop" in element.dataset) { - throw new Error( - "Element is already droppable, can't make it droppable again" - ); + export function MakeDropTarget(element: HTMLElement, dropFunc: (e: Event, de: DropEvent) => void, doc?: Doc, preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void): DragDropDisposer { + if ('canDrop' in element.dataset) { + throw new Error("Element is already droppable, can't make it droppable again"); } - element.dataset.canDrop = "true"; + element.dataset.canDrop = 'true'; const handler = (e: Event) => dropFunc(e, (e as CustomEvent).detail); const preDropHandler = (e: Event) => { const de = (e as CustomEvent).detail; preDropFunc?.(e, de, StrCast(doc?.targetDropAction) as dropActionType); }; - element.addEventListener("dashOnDrop", handler); - doc && element.addEventListener("dashPreDrop", preDropHandler); + element.addEventListener('dashOnDrop', handler); + doc && element.addEventListener('dashPreDrop', preDropHandler); return () => { - element.removeEventListener("dashOnDrop", handler); - doc && element.removeEventListener("dashPreDrop", preDropHandler); + element.removeEventListener('dashOnDrop', handler); + doc && element.removeEventListener('dashPreDrop', preDropHandler); delete element.dataset.canDrop; }; } // drag a document and drop it (or make an alias/copy on drop) - export function StartDocumentDrag( - eles: HTMLElement[], - dragData: DocumentDragData, - downX: number, - downY: number, - options?: DragOptions, - dropEvent?: () => any - ) { + export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, dropEvent?: () => any) { const addAudioTag = (dropDoc: any) => { - dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); + dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField()); dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc); return dropDoc; }; @@ -222,19 +201,27 @@ export namespace DragManager { dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; - docDragData.droppedDocuments = - await Promise.all(dragData.draggedDocuments.map(async d => - !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? - addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : - docDragData.dropAction === "alias" ? Doc.MakeAlias(d) : - docDragData.dropAction === "proto" ? Doc.GetProto(d) : - docDragData.dropAction === "copy" ? - (await Doc.MakeClone(d)).clone : d)); - !["same", "proto"].includes(docDragData.dropAction as any) && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { - const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []); - const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps)); - remProps.map(prop => drop[prop] = undefined); - }); + docDragData.droppedDocuments = await Promise.all( + dragData.draggedDocuments.map(async d => + !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) + ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) + : docDragData.dropAction === 'alias' + ? Doc.MakeAlias(d) + : docDragData.dropAction === 'proto' + ? Doc.GetProto(d) + : docDragData.dropAction === 'copy' + ? ( + await Doc.MakeClone(d) + ).clone + : d + ) + ); + !['same', 'proto'].includes(docDragData.dropAction as any) && + docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { + const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec('string'), []); + const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps)); + remProps.map(prop => (drop[prop] = undefined)); + }); } return e; }; @@ -243,23 +230,22 @@ export namespace DragManager { return true; } - // drag a button template and drop a new button - export function - StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { + // drag a button template and drop a new button + export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { const finishDrag = (e: DragCompleteEvent) => { const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }); params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields initialize?.(bd); - Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List(params); + Doc.GetProto(bd)['onClick-paramFieldKeys'] = new List(params); e.docDragData && (e.docDragData.droppedDocuments = [bd]); return e; }; options = options ?? {}; - options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate + options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag); } - // drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption + // drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption export function StartAnchorAnnoDrag(eles: HTMLElement[], dragData: AnchorAnnoDragData, downX: number, downY: number, options?: DragOptions) { StartDrag(eles, dragData, downX, downY, options); } @@ -286,8 +272,8 @@ export namespace DragManager { let near = dragPt; const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => { if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0 - const denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)); - if (denominator === 0) return undefined; // Lines are parallel + const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + if (denominator === 0) return undefined; // Lines are parallel const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator; // let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator; @@ -315,14 +301,14 @@ export namespace DragManager { }); return { x: near[0], y: near[1] }; } - // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line + // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { const snapThreshold = Utils.SNAP_THRESHOLD; const snapVal = (pts: number[], drag: number, snapLines: number[]) => { if (snapLines.length) { - const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt + const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt const rangePts = [drag - offs[0], drag - offs[1], drag - offs[2]]; // left, mid, right or top, mid, bottom pts to try to snap to snaplines - const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest)); + const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => (Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest))); const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i])); const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2; return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag; @@ -331,55 +317,61 @@ export namespace DragManager { }; return { x: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()), - y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines()) + y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines()), }; } export let docsBeingDragged: Doc[] = []; export let CanEmbed = false; export let DocDragData: DocumentDragData | undefined; export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { - if (dragData.dropAction === "none") return; + if (dragData.dropAction === 'none') return; DocDragData = dragData instanceof DocumentDragData ? dragData : undefined; - const batch = UndoManager.StartBatch("dragging"); + const batch = UndoManager.StartBatch('dragging'); eles = eles.filter(e => e); CanEmbed = dragData.canEmbed || false; if (!dragDiv) { - dragDiv = document.createElement("div"); - dragDiv.className = "dragManager-dragDiv"; - dragDiv.style.pointerEvents = "none"; - dragLabel = document.createElement("div"); - dragLabel.className = "dragManager-dragLabel"; - dragLabel.style.zIndex = "100001"; - dragLabel.style.fontSize = "10px"; - dragLabel.style.position = "absolute"; - dragLabel.innerText = "drag titlebar to embed on drop"; // bcz: need to move this to a status bar + dragDiv = document.createElement('div'); + dragDiv.className = 'dragManager-dragDiv'; + dragDiv.style.pointerEvents = 'none'; + dragLabel = document.createElement('div'); + dragLabel.className = 'dragManager-dragLabel'; + dragLabel.style.zIndex = '100001'; + dragLabel.style.fontSize = '10px'; + dragLabel.style.position = 'absolute'; + dragLabel.innerText = 'drag titlebar to embed on drop'; // bcz: need to move this to a status bar dragDiv.appendChild(dragLabel); DragManager.Root().appendChild(dragDiv); } - Object.assign(dragDiv.style, { width: "", height: "", overflow: "" }); + Object.assign(dragDiv.style, { width: '', height: '', overflow: '' }); dragDiv.hidden = false; - const scaleXs: number[] = [], scaleYs: number[] = [], xs: number[] = [], ys: number[] = []; + const scaleXs: number[] = [], + scaleYs: number[] = [], + xs: number[] = [], + ys: number[] = []; docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : []; const elesCont = { - left: Number.MAX_SAFE_INTEGER, right: Number.MIN_SAFE_INTEGER, - top: Number.MAX_SAFE_INTEGER, bottom: Number.MIN_SAFE_INTEGER + left: Number.MAX_SAFE_INTEGER, + right: Number.MIN_SAFE_INTEGER, + top: Number.MAX_SAFE_INTEGER, + bottom: Number.MIN_SAFE_INTEGER, }; const dragElements = eles.map(ele => { if (!ele.parentNode) dragDiv.appendChild(ele); - const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement; + const dragElement = ele.parentNode === dragDiv ? ele : (ele.cloneNode(true) as HTMLElement); 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 + 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) { - ["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")); + ['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; + if (next.localName.startsWith('marker')) { + next.id = 'X' + next.id; } } } @@ -396,20 +388,31 @@ export namespace DragManager { scaleXs.push(scaleX); scaleYs.push(scaleY); Object.assign(dragElement.style, { - opacity: "0.7", position: "absolute", margin: "0", top: "0", bottom: "", left: "0", color: "black", transition: "none", - borderRadius: getComputedStyle(ele).borderRadius, zIndex: globalCssVariables.contextMenuZindex, - transformOrigin: "0 0", width: `${rect.width / scaleX}px`, height: `${rect.height / scaleY}px`, + opacity: '0.7', + position: 'absolute', + margin: '0', + top: '0', + bottom: '', + left: '0', + color: 'black', + transition: 'none', + borderRadius: getComputedStyle(ele).borderRadius, + zIndex: globalCssVariables.contextMenuZindex, + transformOrigin: '0 0', + width: `${rect.width / scaleX}px`, + height: `${rect.height / scaleY}px`, transform: `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`, }); dragLabel.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0) - 20}px)`; if (docsBeingDragged.length) { - const pdfBox = dragElement.getElementsByTagName("canvas"); - const pdfBoxSrc = ele.getElementsByTagName("canvas"); - Array.from(pdfBox).filter(pb => pb.width && pb.height).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); + const pdfBox = dragElement.getElementsByTagName('canvas'); + const pdfBoxSrc = ele.getElementsByTagName('canvas'); + Array.from(pdfBox) + .filter(pb => pb.width && pb.height) + .map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); } - [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => - (ele as any).style && ((ele as any).style.pointerEvents = "none")); + [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none')); dragDiv.appendChild(dragElement); if (dragElement !== ele) { @@ -427,9 +430,9 @@ export namespace DragManager { }); const hideDragShowOriginalElements = (hide: boolean) => { - dragLabel.style.display = hide ? "" : "none"; + dragLabel.style.display = hide ? '' : 'none'; !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); - eles.forEach(ele => ele.hidden = hide); + eles.forEach(ele => (ele.hidden = hide)); }; options?.hideSource && hideDragShowOriginalElements(true); @@ -448,8 +451,8 @@ export namespace DragManager { const cleanupDrag = action(() => { hideDragShowOriginalElements(false); - document.removeEventListener("pointermove", moveHandler, true); - document.removeEventListener("pointerup", upHandler); + document.removeEventListener('pointermove', moveHandler, true); + document.removeEventListener('pointerup', upHandler, true); SnappingManager.SetIsDragging(false); SnappingManager.clearSnapLines(); batch.end(); @@ -458,17 +461,17 @@ export namespace DragManager { const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { - dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction; + dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.ctrlKey ? 'alias' : dragData.defaultDropAction; } - if (((e.target as any)?.className === "lm_tabs" || (e.target as any)?.className === "lm_header" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { + if (((e.target as any)?.className === 'lm_tabs' || (e.target as any)?.className === 'lm_header' || e?.shiftKey) && dragData.draggedDocuments.length === 1) { if (!startWindowDragTimer) { startWindowDragTimer = setTimeout(async () => { startWindowDragTimer = undefined; - dragData.dropAction = dragData.userDropAction || "same"; + dragData.dropAction = dragData.userDropAction || 'same'; AbortDrag(); await finishDrag?.(new DragCompleteEvent(true, dragData)); - DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => { - if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) { + DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, aborted => { + if (!aborted && (dragData.dropAction === 'move' || dragData.dropAction === 'same')) { dragData.removeDocument?.(dragData.draggedDocuments[0]); } }); @@ -484,7 +487,7 @@ export namespace DragManager { if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) { const autoScrollHandler = () => { target.dispatchEvent( - new CustomEvent("dashDragAutoScroll", { + new CustomEvent('dashDragAutoScroll', { bubbles: true, detail: { shiftKey: e.shiftKey, @@ -493,7 +496,7 @@ export namespace DragManager { ctrlKey: e.ctrlKey, clientX: e.clientX, clientY: e.clientY, - dataTransfer: new DataTransfer, + dataTransfer: new DataTransfer(), button: e.button, buttons: e.buttons, getModifierState: e.getModifierState, @@ -505,8 +508,8 @@ export namespace DragManager { screenX: e.screenX, screenY: e.screenY, detail: e.detail, - view: e.view ? e.view : new Window as any, - nativeEvent: new DragEvent("dashDragAutoScroll"), + view: e.view ? e.view : (new Window() as any), + nativeEvent: new DragEvent('dashDragAutoScroll'), currentTarget: target, target: target, bubbles: true, @@ -514,14 +517,14 @@ export namespace DragManager { defaultPrevented: true, eventPhase: e.eventPhase, isTrusted: true, - preventDefault: () => "not implemented for this event" ? false : false, - isDefaultPrevented: () => "not implemented for this event" ? false : false, - stopPropagation: () => "not implemented for this event" ? false : false, - isPropagationStopped: () => "not implemented for this event" ? false : false, + preventDefault: () => ('not implemented for this event' ? false : false), + isDefaultPrevented: () => ('not implemented for this event' ? false : false), + stopPropagation: () => ('not implemented for this event' ? false : false), + isPropagationStopped: () => ('not implemented for this event' ? false : false), persist: emptyFunction, timeStamp: e.timeStamp, - type: "dashDragAutoScroll" - } + type: 'dashDragAutoScroll', + }, }) ); @@ -537,20 +540,18 @@ export namespace DragManager { lastPt = { x, y }; dragLabel.style.transform = `translate(${xs[0] + moveVec.x + (options?.offsetX || 0)}px, ${ys[0] + moveVec.y + (options?.offsetY || 0) - 20}px)`; - dragElements.map((dragElement, i) => (dragElement.style.transform = - `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`) - ); + dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)); }; const upHandler = (e: PointerEvent) => { clearTimeout(startWindowDragTimer); startWindowDragTimer = undefined; dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, cleanupDrag); }; - document.addEventListener("pointermove", moveHandler, true); - document.addEventListener("pointerup", upHandler); + document.addEventListener('pointermove', moveHandler, true); + document.addEventListener('pointerup', upHandler, true); } - async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number, y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) { + async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) { const dropArgs = { bubbles: true, detail: { @@ -560,13 +561,13 @@ export namespace DragManager { altKey: e.altKey, metaKey: e.metaKey, ctrlKey: e.ctrlKey, - embedKey: CanEmbed - } + embedKey: CanEmbed, + }, }; - target.dispatchEvent(new CustomEvent("dashPreDrop", dropArgs)); + target.dispatchEvent(new CustomEvent('dashPreDrop', dropArgs)); await finishDrag?.(complete); - target.dispatchEvent(new CustomEvent("dashOnDrop", dropArgs)); + target.dispatchEvent(new CustomEvent('dashOnDrop', dropArgs)); options?.dragComplete?.(complete); endDrag?.(); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 1a2054676..bd5e8caab 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -219,8 +219,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { const dragItem: HTMLElement[] = []; if (dragArray.length === 1) { const doc = this._itemRef.current || dragArray[0]; - doc.className = miniView ? 'presItem-miniSlide' : 'presItem-slide'; - dragItem.push(doc); + if (doc) { + doc.className = miniView ? 'presItem-miniSlide' : 'presItem-slide'; + dragItem.push(doc); + } } else if (dragArray.length >= 1) { const doc = document.createElement('div'); doc.className = 'presItem-multiDrag'; -- cgit v1.2.3-70-g09d2 From 358f9e266ef264442aea1e2c7d5d959a19f7624c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 22 Jul 2022 14:11:30 -0400 Subject: adjusted native dim scaling slightly combining props.scaling and props.ContentScaling into props.NativeDimScaling and fixing some resizing behaviors for fitWidth freeformviews and native-sized text boxes. Also fixed clicking on presboxe elements to not drag. --- src/client/views/DocumentDecorations.tsx | 3 +- src/client/views/InkingStroke.tsx | 536 +++++++++++++-------- src/client/views/SidebarAnnos.tsx | 4 +- src/client/views/StyleProvider.tsx | 4 +- .../collections/CollectionStackedTimeline.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 11 +- src/client/views/collections/TreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 30 +- .../CollectionMulticolumnView.tsx | 187 +++---- .../CollectionMultirowView.tsx | 180 +++---- src/client/views/nodes/DocumentContentsView.tsx | 5 +- src/client/views/nodes/DocumentLinksButton.tsx | 384 ++++++++------- src/client/views/nodes/DocumentView.tsx | 24 +- src/client/views/nodes/EquationBox.tsx | 90 ++-- src/client/views/nodes/FieldView.tsx | 55 ++- src/client/views/nodes/ImageBox.tsx | 10 +- src/client/views/nodes/MapBox/MapBox.tsx | 9 +- src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 12 +- src/client/views/nodes/ScreenshotBox.tsx | 4 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 25 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 10 +- src/client/views/nodes/trails/PresElementBox.tsx | 8 +- src/client/views/pdf/PDFViewer.tsx | 17 +- 25 files changed, 883 insertions(+), 741 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 780dcfb6d..964fd36c8 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -521,7 +521,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } else doc._height = actualdH; } } else { - dH && (doc._height = actualdH); + const maxHeight = Math.max(nheight, NumCast(doc.scrollHeight)) * docView.NativeDimScaling(); + dH && (doc._height = actualdH > maxHeight ? maxHeight : actualdH); dW && (doc._width = actualdW); dH && (doc._autoHeight = false); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index dace9716a..e5de7a0c5 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -20,35 +20,37 @@ Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class */ -import React = require("react"); -import { action, IReactionDisposer, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, WidthSym } from "../../fields/Doc"; -import { InkData, InkField, InkTool } from "../../fields/InkField"; -import { Cast, NumCast, RTFCast, StrCast } from "../../fields/Types"; -import { TraceMobx } from "../../fields/util"; -import { OmitKeys, returnFalse, setupMoveUpEvents } from "../../Utils"; -import { CognitiveServices } from "../cognitive_services/CognitiveServices"; -import { InteractionUtils } from "../util/InteractionUtils"; -import { SnappingManager } from "../util/SnappingManager"; -import { Transform } from "../util/Transform"; -import { UndoManager } from "../util/UndoManager"; -import { ContextMenu } from "./ContextMenu"; -import { ViewBoxBaseComponent } from "./DocComponent"; -import { Colors } from "./global/globalEnums"; -import { InkControlPtHandles, InkEndPtHandles } from "./InkControlPtHandles"; -import { InkStrokeProperties } from "./InkStrokeProperties"; -import { InkTangentHandles } from "./InkTangentHandles"; -import { DocComponentView } from "./nodes/DocumentView"; -import { FieldView, FieldViewProps } from "./nodes/FieldView"; -import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; -import "./InkStroke.scss"; -import Color = require("color"); +import React = require('react'); +import { action, IReactionDisposer, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, WidthSym } from '../../fields/Doc'; +import { InkData, InkField, InkTool } from '../../fields/InkField'; +import { Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; +import { TraceMobx } from '../../fields/util'; +import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils'; +import { CognitiveServices } from '../cognitive_services/CognitiveServices'; +import { InteractionUtils } from '../util/InteractionUtils'; +import { SnappingManager } from '../util/SnappingManager'; +import { Transform } from '../util/Transform'; +import { UndoManager } from '../util/UndoManager'; +import { ContextMenu } from './ContextMenu'; +import { ViewBoxBaseComponent } from './DocComponent'; +import { Colors } from './global/globalEnums'; +import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles'; +import { InkStrokeProperties } from './InkStrokeProperties'; +import { InkTangentHandles } from './InkTangentHandles'; +import { DocComponentView } from './nodes/DocumentView'; +import { FieldView, FieldViewProps } from './nodes/FieldView'; +import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import './InkStroke.scss'; +import Color = require('color'); @observer export class InkingStroke extends ViewBoxBaseComponent() { static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big) - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(InkingStroke, fieldStr); + } public static IsClosed(inkData: InkData) { return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; } @@ -56,13 +58,15 @@ export class InkingStroke extends ViewBoxBaseComponent() { private _selDisposer?: IReactionDisposer; @observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight) - @observable _nearestT?: number; // nearest t value within the nearest Bezier segment " - @observable _nearestScrPt?: { X: number, Y: number }; // nearst screen point on the ink stroke "" + @observable _nearestT?: number; // nearest t value within the nearest Bezier segment " + @observable _nearestScrPt?: { X: number; Y: number }; // nearst screen point on the ink stroke "" componentDidMount() { this.props.setContentView?.(this); - this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles - selected => !selected && (InkStrokeProperties.Instance._controlButton = false)); + this._selDisposer = reaction( + () => this.props.isSelected(), // react to stroke being deselected by turning off ink handles + selected => !selected && (InkStrokeProperties.Instance._controlButton = false) + ); } componentWillUnmount() { this._selDisposer?.(); @@ -70,36 +74,36 @@ export class InkingStroke extends ViewBoxBaseComponent() { // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc) - screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1); + screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1); getAnchor = () => { console.log(document.activeElement); return this._subContentView?.getAnchor?.() || this.rootDoc; - } + }; scrollFocus = (textAnchor: Doc, smooth: boolean) => { return this._subContentView?.scrollFocus?.(textAnchor, smooth); - } + }; /** - * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space); - * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since - * the center of the ink stroke changes as the stroke is rotated. - */ + * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space); + * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since + * the center of the ink stroke changes as the stroke is rotated. + */ getCenter = (xf: Transform) => { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); const angle = -NumCast(this.layoutDoc.rotation); const newPoints = inkData.map(pt => { - const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * inkScaleY / inkScaleX; - const newY = Math.sin(angle) * pt.X * inkScaleX / inkScaleY + Math.cos(angle) * pt.Y; + const newX = Math.cos(angle) * pt.X - (Math.sin(angle) * pt.Y * inkScaleY) / inkScaleX; + const newY = (Math.sin(angle) * pt.X * inkScaleX) / inkScaleY + Math.cos(angle) * pt.Y; return { X: newX, Y: newY }; }); const crx = (Math.max(...newPoints.map(np => np.X)) + Math.min(...newPoints.map(np => np.X))) / 2; const cry = (Math.max(...newPoints.map(np => np.Y)) + Math.min(...newPoints.map(np => np.Y))) / 2; - const cx = Math.cos(-angle) * crx - Math.sin(-angle) * cry * inkScaleY / inkScaleX; - const cy = Math.sin(-angle) * crx * inkScaleX / inkScaleY + Math.cos(-angle) * cry; + const cx = Math.cos(-angle) * crx - (Math.sin(-angle) * cry * inkScaleY) / inkScaleX; + const cy = (Math.sin(-angle) * crx * inkScaleX) / inkScaleY + Math.cos(-angle) * cry; const tc = xf.transformPoint((cx - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (cy - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2); return { X: tc[0], Y: tc[1] }; - } + }; /** * analyzes the ink stroke and saves the analysis of the stroke to the 'inkAnalysis' field, @@ -107,7 +111,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { */ analyzeStrokes() { const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; - CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]); + CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ['inkAnalysis', 'handwriting'], [data]); } /** @@ -115,12 +119,12 @@ export class InkingStroke extends ViewBoxBaseComponent() { * When displayed as a mask, the stroke is rendered with mixBlendMode set to multiply so that the stroke will * appear to illuminate what it covers up. At the same time, all pixels that are not under the stroke will be * dimmed by a semi-opaque overlay mask. - */ + */ public static toggleMask = action((inkDoc: Doc) => { inkDoc.isInkMask = !inkDoc.isInkMask; - inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined; - inkDoc.mixBlendMode = inkDoc.isInkMask ? "hard-light" : undefined; - inkDoc.color = "#9b9b9bff"; + inkDoc._backgroundColor = inkDoc.isInkMask ? 'rgba(0,0,0,0.7)' : undefined; + inkDoc.mixBlendMode = inkDoc.isInkMask ? 'hard-light' : undefined; + inkDoc.color = '#9b9b9bff'; inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined; }); /** @@ -132,46 +136,60 @@ export class InkingStroke extends ViewBoxBaseComponent() { this._handledClick = false; const inkView = this.props.docViewPath().lastElement(); const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenPts = inkData.map(point => this.screenToLocal().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 screenPts = inkData + .map(point => + this.screenToLocal() + .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 { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); const controlIndex = nearestSeg; const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex; var controlUndo: UndoManager.Batch | undefined; const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected(); - setupMoveUpEvents(this, e, - !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => { - if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); - const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] }); - const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 }); - InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex); - InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3); - return false; - }), - !isEditing ? returnFalse : action(() => { - controlUndo?.end(); - controlUndo = undefined; - UndoManager.FilterBatches(["data", "x", "y", "width", "height"]); - }), + setupMoveUpEvents( + this, + e, + !isEditing + ? returnFalse + : action((e: PointerEvent, down: number[], delta: number[]) => { + if (!controlUndo) controlUndo = UndoManager.StartBatch('drag ink ctrl pt'); + const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] }); + const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 }); + InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex); + InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3); + return false; + }), + !isEditing + ? returnFalse + : action(() => { + controlUndo?.end(); + controlUndo = undefined; + UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); + }), action((e: PointerEvent, doubleTap: boolean | undefined) => { doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick; if (doubleTap) { InkStrokeProperties.Instance._controlButton = true; InkStrokeProperties.Instance._currentPoint = -1; - this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView + this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView if (isEditing) { this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice()); } } - }), isEditing, isEditing, action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1))); - } + }), + isEditing, + isEditing, + action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1)) + ); + }; /** * @param scrPt a point in the screen coordinate space * @returns the point in the ink data's coordinate space. */ - ptFromScreen = (scrPt: { X: number, Y: number }) => { + ptFromScreen = (scrPt: { X: number; Y: number }) => { const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); const docPt = this.screenToLocal().transformPoint(scrPt.X, scrPt.Y); const inkPt = { @@ -179,39 +197,39 @@ export class InkingStroke extends ViewBoxBaseComponent() { Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop, }; return inkPt; - } + }; /** * @param inkPt a point in the ink data's coordinate space * @returns the screen point corresponding to the ink point */ - ptToScreen = (inkPt: { X: number, Y: number }) => { + ptToScreen = (inkPt: { X: number; Y: number }) => { const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); const docPt = { X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, - Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2 + Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2, }; const scrPt = this.screenToLocal().inverse().transformPoint(docPt.X, docPt.Y); return { X: scrPt[0], Y: scrPt[1] }; - } + }; /** - * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs' - * @param scrPt - the point to snap to this stroke - * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments) - * - * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point - */ - snapPt = (scrPt: { X: number, Y: number }, excludeSegs?: number[]) => { + * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs' + * @param scrPt - the point to snap to this stroke + * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments) + * + * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point + */ + snapPt = (scrPt: { X: number; Y: number }, excludeSegs?: number[]) => { const { inkData } = this.inkScaledData(); const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []); return { nearestPt, distance: distance * this.screenToLocal().inverse().Scale }; - } + }; /** - * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale - * factor for converting between ink and screen space. - */ + * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale + * factor for converting between ink and screen space. + */ inkScaledData = () => { const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1); @@ -228,27 +246,31 @@ export class InkingStroke extends ViewBoxBaseComponent() { inkLeft, inkWidth, inkHeight, - inkScaleX: ((this.props.PanelWidth() - inkStrokeWidth) / ((inkWidth - inkStrokeWidth) || 1) || 1), - inkScaleY: ((this.props.PanelHeight() - inkStrokeWidth) / ((inkHeight - inkStrokeWidth) || 1) || 1) + inkScaleX: (this.props.PanelWidth() - inkStrokeWidth) / (inkWidth - inkStrokeWidth || 1) || 1, + inkScaleY: (this.props.PanelHeight() - inkStrokeWidth) / (inkHeight - inkStrokeWidth || 1) || 1, }; - } + }; // - // this updates the highlight for the nearest point on the curve to the cursor. + // this updates the highlight for the nearest point on the curve to the cursor. // if the user double clicks, this highlighted point will be added as a control point in the curve. // @action onPointerMove = (e: React.PointerEvent) => { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenPts = inkData.map(point => this.screenToLocal().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 screenPts = inkData + .map(point => + this.screenToLocal() + .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 { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); this._nearestT = nearestT; this._nearestSeg = nearestSeg; this._nearestScrPt = nearestPt; - } + }; /** * @returns the nearest screen point to the cursor (to render a highlight for the point to be added) @@ -263,50 +285,66 @@ export class InkingStroke extends ViewBoxBaseComponent() { componentUI = (boundsLeft: number, boundsTop: number) => { const inkDoc = this.props.Document; const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke + const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke const screenInkWidth = this.screenToLocal().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth); - const screenPts = inkData.map(point => this.screenToLocal().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 screenPts = inkData + .map(point => + this.screenToLocal() + .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 screenHdlPts = screenPts; const startMarker = StrCast(this.layoutDoc.strokeStartMarker); const endMarker = StrCast(this.layoutDoc.strokeEndMarker); const markerScale = NumCast(this.layoutDoc.strokeMarkerScale); - return SnappingManager.GetIsDragging() ? (null) : - !InkStrokeProperties.Instance._controlButton ? - (!this.props.isSelected() || InkingStroke.IsClosed(inkData) ? (null) : -
- -
) : + return SnappingManager.GetIsDragging() ? null : !InkStrokeProperties.Instance._controlButton ? ( + !this.props.isSelected() || InkingStroke.IsClosed(inkData) ? 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, markerScale * Math.min(screenSpaceCenterlineStrokeWidth, screenInkWidth[0] / screenSpaceCenterlineStrokeWidth), StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)} - - -
; - } + +
+ ) + ) : ( +
+ {InteractionUtils.CreatePolyline( + screenPts, + 0, + 0, + Colors.MEDIUM_BLUE, + screenInkWidth[0], + screenSpaceCenterlineStrokeWidth, + StrCast(inkDoc.strokeLineJoin), + StrCast(this.layoutDoc.strokeLineCap), + StrCast(inkDoc.strokeBezier), + 'none', + startMarker, + endMarker, + markerScale * Math.min(screenSpaceCenterlineStrokeWidth, screenInkWidth[0] / screenSpaceCenterlineStrokeWidth), + StrCast(inkDoc.strokeDash), + 1, + 1, + '', + 'none', + 1.0, + false + )} + + +
+ ); + }; _subContentView: DocComponentView | undefined; - setSubContentView = (doc: DocComponentView) => this._subContentView = doc; + setSubContentView = (doc: DocComponentView) => (this._subContentView = doc); render() { TraceMobx(); const { inkData, inkStrokeWidth, inkLeft, inkTop, inkScaleX, inkScaleY, inkWidth, inkHeight } = this.inkScaledData(); @@ -315,105 +353,181 @@ export class InkingStroke extends ViewBoxBaseComponent() { const endMarker = StrCast(this.layoutDoc.strokeEndMarker); const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1); const closed = InkingStroke.IsClosed(inkData); - const fillColor = StrCast(this.layoutDoc.fillColor, "transparent"); - const strokeColor = !closed && fillColor && fillColor !== "transparent" ? fillColor : StrCast(this.layoutDoc.color); + const fillColor = StrCast(this.layoutDoc.fillColor, 'transparent'); + const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : 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.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), - StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker, - markerScale, StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false); + const inkLine = InteractionUtils.CreatePolyline( + inkData, + inkLeft, + inkTop, + strokeColor, + inkStrokeWidth, + inkStrokeWidth, + StrCast(this.layoutDoc.strokeLineJoin), + StrCast(this.layoutDoc.strokeLineCap), + StrCast(this.layoutDoc.strokeBezier), + !closed ? 'none' : fillColor === 'transparent' ? 'none' : fillColor, + startMarker, + endMarker, + markerScale, + StrCast(this.layoutDoc.strokeDash), + inkScaleX, + inkScaleY, + '', + 'none', + 1.0, + false + ); const highlightIndex = /*BoolCast(this.props.Document.isLinkButton) && */ Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString - const highlightColor = !highlightIndex ? - StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") : - ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex]; + const highlightColor = !highlightIndex + ? StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== 'transparent' ? StrCast(this.layoutDoc.color, 'transparent') : 'transparent') + : ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'yellow', 'magenta', 'cyan', 'orange'][highlightIndex]; // Invisible polygonal line that enables the ink to be selected by the user. - const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) => InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor, - inkStrokeWidth, fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? closed ? 0 : (highlightIndex + 2) : 0), - StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), - StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" || suppressFill ? "none" : fillColor, startMarker, endMarker, - markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? "none" : "visiblepainted"), 0.0, - false, downHdlr); - const fsize = +(StrCast(this.props.Document.fontSize, "12px").replace("px", "")); - // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size. + const clickableLine = (downHdlr?: (e: React.PointerEvent) => void, suppressFill: boolean = false) => + InteractionUtils.CreatePolyline( + inkData, + inkLeft, + inkTop, + highlightColor, + inkStrokeWidth, + fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0), + StrCast(this.layoutDoc.strokeLineJoin), + StrCast(this.layoutDoc.strokeLineCap), + StrCast(this.layoutDoc.strokeBezier), + !closed ? 'none' : fillColor === 'transparent' || suppressFill ? 'none' : fillColor, + startMarker, + endMarker, + markerScale, + undefined, + inkScaleX, + inkScaleY, + '', + this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? 'none' : 'visiblepainted'), + 0.0, + false, + downHdlr + ); + const fsize = +StrCast(this.props.Document.fontSize, '12px').replace('px', ''); + // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size. // this attempts to figure out the lineHeight ratio by inquiring the body's lineHeight and dividing by the fontsize which should yield 1.428571429 // see: https://bibwild.wordpress.com/2019/06/10/bootstrap-3-to-4-changes-in-how-font-size-line-height-and-spacing-is-done-or-what-happened-to-line-height-computed/ - const lineHeightGuess = (+getComputedStyle(document.body).lineHeight.replace("px", "")) / (+getComputedStyle(document.body).fontSize.replace("px", "")); + const lineHeightGuess = +getComputedStyle(document.body).lineHeight.replace('px', '') / +getComputedStyle(document.body).fontSize.replace('px', ''); const interactions = { - onPointerLeave: action(() => this._nearestScrPt = undefined), + onPointerLeave: action(() => (this._nearestScrPt = undefined)), onPointerMove: this.props.isSelected() ? this.onPointerMove : undefined, onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(), onContextMenu: () => { const cm = ContextMenu.Instance; - !Doc.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(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" }); - } + !Doc.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(() => (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)), icon: 'paint-brush' }); + }, }; - return
- - {closed ? inkLine : clickableLine(this.onPointerDown)} - {closed ? clickableLine(this.onPointerDown) : inkLine} - - {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? (null) : -
- -
- } - {!closed ? null : - {clickableLine(this.onPointerDown, true)} - } -
; + return ( +
+ + {closed ? inkLine : clickableLine(this.onPointerDown)} + {closed ? clickableLine(this.onPointerDown) : inkLine} + + {!closed || (!RTFCast(this.rootDoc.text)?.Text && !this.props.isSelected()) ? null : ( +
+ +
+ )} + {!closed ? null : ( + + {clickableLine(this.onPointerDown, true)} + + )} +
+ ); } } - -export function SetActiveInkWidth(width: string): void { !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); } -export function SetActiveBezierApprox(bezier: string): void { ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? "" : bezier); } -export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); } -export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); } -export function SetActiveArrowStart(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); } -export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); } -export function SetActiveArrowScale(value: number) { ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); } -export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); } -export function ActiveInkPen(): Doc { return Doc.UserDoc(); } -export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); } -export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); } -export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); } -export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); } -export function ActiveArrowScale(): number { return NumCast(ActiveInkPen()?.activeArrowScale, 1); } -export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); } -export function ActiveInkWidth(): number { return Number(ActiveInkPen()?.activeInkWidth); } -export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } +export function SetActiveInkWidth(width: string): void { + !isNaN(parseInt(width)) && ActiveInkPen() && (ActiveInkPen().activeInkWidth = width); +} +export function SetActiveBezierApprox(bezier: string): void { + ActiveInkPen() && (ActiveInkPen().activeInkBezier = isNaN(parseInt(bezier)) ? '' : bezier); +} +export function SetActiveInkColor(value: string) { + ActiveInkPen() && (ActiveInkPen().activeInkColor = value); +} +export function SetActiveFillColor(value: string) { + ActiveInkPen() && (ActiveInkPen().activeFillColor = value); +} +export function SetActiveArrowStart(value: string) { + ActiveInkPen() && (ActiveInkPen().activeArrowStart = value); +} +export function SetActiveArrowEnd(value: string) { + ActiveInkPen() && (ActiveInkPen().activeArrowEnd = value); +} +export function SetActiveArrowScale(value: number) { + ActiveInkPen() && (ActiveInkPen().activeArrowScale = value); +} +export function SetActiveDash(dash: string): void { + !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); +} +export function ActiveInkPen(): Doc { + return Doc.UserDoc(); +} +export function ActiveInkColor(): string { + return StrCast(ActiveInkPen()?.activeInkColor, 'black'); +} +export function ActiveFillColor(): string { + return StrCast(ActiveInkPen()?.activeFillColor, ''); +} +export function ActiveArrowStart(): string { + return StrCast(ActiveInkPen()?.activeArrowStart, ''); +} +export function ActiveArrowEnd(): string { + return StrCast(ActiveInkPen()?.activeArrowEnd, ''); +} +export function ActiveArrowScale(): number { + return NumCast(ActiveInkPen()?.activeArrowScale, 1); +} +export function ActiveDash(): string { + return StrCast(ActiveInkPen()?.activeDash, '0'); +} +export function ActiveInkWidth(): number { + return Number(ActiveInkPen()?.activeInkWidth); +} +export function ActiveInkBezierApprox(): string { + return StrCast(ActiveInkPen()?.activeInkBezier); +} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index e81a9c40f..9fdf9d2be 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -93,7 +93,7 @@ export class SidebarAnnos extends React.Component { this.props .ScreenToLocalTransform() .translate(Doc.NativeWidth(this.props.dataDoc), 0) - .scale(this.props.scaling?.() || 1); + .scale(this.props.NativeDimScaling?.() || 1); // panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 : // this.props.usePanelWidth ? this.props.PanelWidth() : // (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth); @@ -164,7 +164,7 @@ export class SidebarAnnos extends React.Component { setHeight={this.setHeightCallback} isAnnotationOverlay={false} select={emptyFunction} - scaling={returnOne} + NativeDimScaling={returnOne} childShowTitle={this.showTitle} childDocumentsActive={this.props.isContentActive} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 340a5df45..334f381be 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -250,9 +250,9 @@ export function DefaultStyleProvider(doc: Opt, props: Opt (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins); truncateTitleWidth = () => this.treeViewtruncateTitleWidth; onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); - panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.scaling?.() || 1); + panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.NativeDimScaling?.() || 1); addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; @@ -389,9 +390,9 @@ export class CollectionTreeView extends CollectionSubView {!this.buttonMenu && !this.noviceExplainer ? null : ( diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 5a2103e98..aa1330762 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -403,7 +403,7 @@ export class TreeView extends React.Component { const aspect = Doc.NativeAspect(layoutDoc); if (layoutDoc._fitWidth) return Math.min(this.props.panelWidth() - treeBulletWidth(), layoutDoc[WidthSym]()); if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.panelWidth() - treeBulletWidth())); - return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]()); + return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]()); }; docHeight = () => { const layoutDoc = this.layoutDoc; @@ -514,7 +514,7 @@ export class TreeView extends React.Component { rtfWidth = () => { const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; - return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1); + return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1); }; rtfHeight = () => { const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; @@ -921,7 +921,6 @@ export class TreeView extends React.Component { searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} ContainingCollectionDoc={this.props.treeView.props.Document} - ContentScaling={returnOne} /> ); @@ -992,7 +991,6 @@ export class TreeView extends React.Component { hideResizeHandles={this.props.treeView.outlineMode} onClick={this.onChildClick} focus={this.refocus} - ContentScaling={returnOne} onKey={this.onKeyDown} hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5d7a12122..3e938ec1c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -58,7 +58,7 @@ import e = require('connect-flash'); export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) viewDefDivClick?: ScriptField; - childPointerEvents?: boolean; + childPointerEvents?: string; scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; @@ -150,11 +150,11 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; + if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); @@ -1251,7 +1251,7 @@ export class CollectionFreeFormView extends CollectionSubView { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); - const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ? 'all' : this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.(); + const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.()); return pointerEvents; }; getChildDocView(entry: PoolData) { @@ -1631,9 +1631,11 @@ export class CollectionFreeFormView extends CollectionSubView {this._firstRender ? this.placeholder : this.marqueeView} {this.props.noOverlay ? null : } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 777ef464f..465dbfe6d 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -1,6 +1,6 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; -import * as React from "react"; +import * as React from 'react'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; @@ -11,11 +11,10 @@ import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; import { CollectionSubView } from '../CollectionSubView'; -import "./CollectionMulticolumnView.scss"; +import './CollectionMulticolumnView.scss'; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; - interface WidthSpecifier { magnitude: number; unit: string; @@ -27,8 +26,8 @@ interface LayoutData { } export const DimUnit = { - Pixel: "px", - Ratio: "*" + Pixel: 'px', + Ratio: '*', }; const resolvedUnits = Object.values(DimUnit); @@ -36,14 +35,13 @@ const resizerWidth = 8; @observer export class CollectionMulticolumnView extends CollectionSubView() { - /** * @returns the list of layout documents whose width unit is * *, denoting that it will be displayed with a ratio, not fixed pixel, value */ @computed private get ratioDefinedDocs() { - return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, "*") === DimUnit.Ratio); + return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio); } @computed @@ -65,10 +63,10 @@ export class CollectionMulticolumnView extends CollectionSubView() { let starSum = 0; const widthSpecifiers: WidthSpecifier[] = []; this.childLayoutPairs.map(pair => { - const unit = StrCast(pair.layout._dimUnit, "*"); + const unit = StrCast(pair.layout._dimUnit, '*'); const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim); if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) { - (unit === DimUnit.Ratio) && (starSum += magnitude); + unit === DimUnit.Ratio && (starSum += magnitude); widthSpecifiers.push({ magnitude, unit }); } /** @@ -100,14 +98,13 @@ export class CollectionMulticolumnView extends CollectionSubView() { * This returns the total quantity, in pixels, that this * view needs to reserve for child documents that have * (with higher priority) requested a fixed pixel width. - * + * * If the underlying resolvedLayoutInformation returns null * because we're waiting on promises to resolve, this value will be undefined as well. */ @computed private get totalFixedAllocation(): number | undefined { - return this.resolvedLayoutInformation?.widthSpecifiers.reduce( - (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0); + return this.resolvedLayoutInformation?.widthSpecifiers.reduce((sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0); } /** @@ -115,7 +112,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { * view needs to reserve for child documents that have * (with lower priority) requested a certain relative proportion of the * remaining pixel width not allocated for fixed widths. - * + * * If the underlying totalFixedAllocation returns undefined * because we're waiting indirectly on promises to resolve, this value will be undefined as well. */ @@ -135,7 +132,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { * this accessor returns 1000 / (2 + 2 + 1), or 200px. * Elsewhere, this is then multiplied by each relative-width * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px). - * + * * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined * because we're waiting indirectly on promises to resolve, this value will be undefined as well. */ @@ -165,17 +162,17 @@ export class CollectionMulticolumnView extends CollectionSubView() { return 0; // we're still waiting on promises to resolve } let width = NumCast(layout._dimMagnitude, this.minimumDim); - if (StrCast(layout._dimUnit, "*") === DimUnit.Ratio) { + if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) { width *= columnUnitLength; } return width; - } + }; /** * @returns the transform that will correctly place * the document decorations box, shifted to the right by * the sum of all the resolved column widths of the - * documents before the target. + * documents before the target. */ private lookupIndividualTransform = (layout: Doc) => { const columnUnitLength = this.columnUnitLength; @@ -185,12 +182,12 @@ export class CollectionMulticolumnView extends CollectionSubView() { let offset = 0; for (const { layout: candidate } of this.childLayoutPairs) { if (candidate === layout) { - return this.props.ScreenToLocalTransform().translate(-offset / (this.props.scaling?.() || 1), 0); + return this.props.ScreenToLocalTransform().translate(-offset / (this.props.NativeDimScaling?.() || 1), 0); } offset += this.lookupPixels(candidate) + resizerWidth; } return Transform.Identity(); // type coersion, this case should never be hit - } + }; @undoBatch @action @@ -198,16 +195,17 @@ export class CollectionMulticolumnView extends CollectionSubView() { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { let curInd = -1; - de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - curInd = this.childDocs.indexOf(d); - })); + de.complete.docDragData?.droppedDocuments.forEach( + action((d: Doc) => { + curInd = this.childDocs.indexOf(d); + }) + ); Array.from(this._mainCont.children).forEach((child, index) => { const brect = child.getBoundingClientRect(); if (brect.x < de.x && brect.x + brect.width > de.x) { if (curInd !== -1 && curInd === Math.floor(index / 2)) { dropInd = curInd; - } - else if (child.className === "multiColumnResizer") { + } else if (child.className === 'multiColumnResizer') { dropInd = Math.floor(index / 2); } else { dropInd = Math.ceil(index / 2 + (de.x - brect.x > brect.width / 2 ? 0 : -1)); @@ -215,76 +213,80 @@ export class CollectionMulticolumnView extends CollectionSubView() { } }); if (super.onInternalDrop(e, de)) { - de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - d._dimUnit = "*"; - d._dimMagnitude = 1; - if (dropInd !== curInd || dropInd === -1) { - if (this.childDocs.includes(d)) { - if (dropInd > this.childDocs.indexOf(d)) dropInd--; + de.complete.docDragData?.droppedDocuments.forEach( + action((d: Doc) => { + d._dimUnit = '*'; + d._dimMagnitude = 1; + if (dropInd !== curInd || dropInd === -1) { + if (this.childDocs.includes(d)) { + if (dropInd > this.childDocs.indexOf(d)) dropInd--; + } + Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); + Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); } - Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); - Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); - } - })); + }) + ); } } return false; - } - + }; onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); addDocTab = (doc: Doc, where: string) => { - if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { this.dataDoc[this.props.fieldKey] = new List([doc]); return true; } return this.props.addDocTab(doc, where); - } + }; isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + isChildContentActive = () => + ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { - return ; - } + return ( + + ); + }; /** * @returns the resolved list of rendered child documents, displayed - * at their resolved pixel widths, each separated by a resizer. + * at their resolved pixel widths, each separated by a resizer. */ @computed private get contents(): JSX.Element[] | null { @@ -293,22 +295,20 @@ export class CollectionMulticolumnView extends CollectionSubView() { const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; - const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1)); + const dxf = () => + this.lookupIndividualTransform(layout) + .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)) + .scale(this.props.NativeDimScaling?.() || 1); const width = () => this.lookupPixels(layout); const height = () => PanelHeight() - 2 * NumCast(Document._yMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); collector.push( -
+
{this.getDisplayDoc(layout, dxf, width, height)} - +
, + marginLeft: NumCast(this.props.Document._xMargin), + marginRight: NumCast(this.props.Document._xMargin), + marginTop: NumCast(this.props.Document._yMargin), + marginBottom: NumCast(this.props.Document._yMargin), + }}> {this.contents}
); } - -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 08385bcb5..f8de4e5de 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -1,6 +1,6 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; -import * as React from "react"; +import * as React from 'react'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; @@ -11,7 +11,7 @@ import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; import { CollectionSubView } from '../CollectionSubView'; -import "./CollectionMultirowView.scss"; +import './CollectionMultirowView.scss'; import HeightLabel from './MultirowHeightLabel'; import ResizeBar from './MultirowResizer'; @@ -26,8 +26,8 @@ interface LayoutData { } export const DimUnit = { - Pixel: "px", - Ratio: "*" + Pixel: 'px', + Ratio: '*', }; const resolvedUnits = Object.values(DimUnit); @@ -35,14 +35,13 @@ const resizerHeight = 8; @observer export class CollectionMultirowView extends CollectionSubView() { - /** * @returns the list of layout documents whose width unit is * *, denoting that it will be displayed with a ratio, not fixed pixel, value */ @computed private get ratioDefinedDocs() { - return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, "*") === DimUnit.Ratio); + return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio); } @computed @@ -64,10 +63,10 @@ export class CollectionMultirowView extends CollectionSubView() { let starSum = 0; const heightSpecifiers: HeightSpecifier[] = []; this.childLayoutPairs.map(pair => { - const unit = StrCast(pair.layout._dimUnit, "*"); + const unit = StrCast(pair.layout._dimUnit, '*'); const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim); if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) { - (unit === DimUnit.Ratio) && (starSum += magnitude); + unit === DimUnit.Ratio && (starSum += magnitude); heightSpecifiers.push({ magnitude, unit }); } /** @@ -99,14 +98,13 @@ export class CollectionMultirowView extends CollectionSubView() { * This returns the total quantity, in pixels, that this * view needs to reserve for child documents that have * (with higher priority) requested a fixed pixel width. - * + * * If the underlying resolvedLayoutInformation returns null * because we're waiting on promises to resolve, this value will be undefined as well. */ @computed private get totalFixedAllocation(): number | undefined { - return this.resolvedLayoutInformation?.heightSpecifiers.reduce( - (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0); + return this.resolvedLayoutInformation?.heightSpecifiers.reduce((sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0); } /** @@ -114,7 +112,7 @@ export class CollectionMultirowView extends CollectionSubView() { * view needs to reserve for child documents that have * (with lower priority) requested a certain relative proportion of the * remaining pixel width not allocated for fixed widths. - * + * * If the underlying totalFixedAllocation returns undefined * because we're waiting indirectly on promises to resolve, this value will be undefined as well. */ @@ -134,7 +132,7 @@ export class CollectionMultirowView extends CollectionSubView() { * this accessor returns 1000 / (2 + 2 + 1), or 200px. * Elsewhere, this is then multiplied by each relative-width * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px). - * + * * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined * because we're waiting indirectly on promises to resolve, this value will be undefined as well. */ @@ -164,17 +162,17 @@ export class CollectionMultirowView extends CollectionSubView() { return 0; // we're still waiting on promises to resolve } let height = NumCast(layout._dimMagnitude, this.minimumDim); - if (StrCast(layout._dimUnit, "*") === DimUnit.Ratio) { + if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) { height *= rowUnitLength; } return height; - } + }; /** * @returns the transform that will correctly place * the document decorations box, shifted to the right by * the sum of all the resolved row widths of the - * documents before the target. + * documents before the target. */ private lookupIndividualTransform = (layout: Doc) => { const rowUnitLength = this.rowUnitLength; @@ -184,13 +182,12 @@ export class CollectionMultirowView extends CollectionSubView() { let offset = 0; for (const { layout: candidate } of this.childLayoutPairs) { if (candidate === layout) { - return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.scaling?.() || 1)); + return this.props.ScreenToLocalTransform().translate(0, -offset / (this.props.NativeDimScaling?.() || 1)); } offset += this.lookupPixels(candidate) + resizerHeight; } return Transform.Identity(); // type coersion, this case should never be hit - } - + }; @undoBatch @action @@ -198,16 +195,17 @@ export class CollectionMultirowView extends CollectionSubView() { let dropInd = -1; if (de.complete.docDragData && this._mainCont) { let curInd = -1; - de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - curInd = this.childDocs.indexOf(d); - })); + de.complete.docDragData?.droppedDocuments.forEach( + action((d: Doc) => { + curInd = this.childDocs.indexOf(d); + }) + ); Array.from(this._mainCont.children).forEach((child, index) => { const brect = child.getBoundingClientRect(); if (brect.y < de.y && brect.y + brect.height > de.y) { if (curInd !== -1 && curInd === Math.floor(index / 2)) { dropInd = curInd; - } - else if (child.className === "multiColumnResizer") { + } else if (child.className === 'multiColumnResizer') { dropInd = Math.floor(index / 2); } else { dropInd = Math.ceil(index / 2 + (de.y - brect.y > brect.height / 2 ? 0 : -1)); @@ -215,75 +213,79 @@ export class CollectionMultirowView extends CollectionSubView() { } }); if (super.onInternalDrop(e, de)) { - de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - d._dimUnit = "*"; - d._dimMagnitude = 1; - if (dropInd !== curInd || dropInd === -1) { - if (this.childDocs.includes(d)) { - if (dropInd > this.childDocs.indexOf(d)) dropInd--; + de.complete.docDragData?.droppedDocuments.forEach( + action((d: Doc) => { + d._dimUnit = '*'; + d._dimMagnitude = 1; + if (dropInd !== curInd || dropInd === -1) { + if (this.childDocs.includes(d)) { + if (dropInd > this.childDocs.indexOf(d)) dropInd--; + } + Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); + Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); } - Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); - Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); - } - })); + }) + ); } } return false; - } - + }; onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick); addDocTab = (doc: Doc, where: string) => { - if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { this.dataDoc[this.props.fieldKey] = new List([doc]); return true; } return this.props.addDocTab(doc, where); - } + }; isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + isChildContentActive = () => + ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { - return ; - } + return ( + + ); + }; /** * @returns the resolved list of rendered child documents, displayed - * at their resolved pixel widths, each separated by a resizer. + * at their resolved pixel widths, each separated by a resizer. */ @computed private get contents(): JSX.Element[] | null { @@ -292,13 +294,14 @@ export class CollectionMultirowView extends CollectionSubView() { const collector: JSX.Element[] = []; for (let i = 0; i < childLayoutPairs.length; i++) { const { layout } = childLayoutPairs[i]; - const dxf = () => this.lookupIndividualTransform(layout).translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)).scale((this.props.scaling?.() || 1)); + const dxf = () => + this.lookupIndividualTransform(layout) + .translate(-NumCast(Document._xMargin), -NumCast(Document._yMargin)) + .scale(this.props.NativeDimScaling?.() || 1); const height = () => this.lookupPixels(layout); const width = () => PanelWidth() - 2 * NumCast(Document._xMargin) - (BoolCast(Document.showWidthLabels) ? 20 : 0); collector.push( -
+
{this.getDisplayDoc(layout, dxf, width, height)}
, @@ -306,7 +309,7 @@ export class CollectionMultirowView extends CollectionSubView() { height={resizerHeight} styleProvider={this.props.styleProvider} isContentActive={this.props.isContentActive} - key={"resizer" + i} + key={'resizer' + i} columnUnitLength={this.getRowUnitLength} toTop={layout} toBottom={childLayoutPairs[i + 1]?.layout} @@ -319,16 +322,19 @@ export class CollectionMultirowView extends CollectionSubView() { render(): JSX.Element { return ( -
+ marginLeft: NumCast(this.props.Document._xMargin), + marginRight: NumCast(this.props.Document._xMargin), + marginTop: NumCast(this.props.Document._yMargin), + marginBottom: NumCast(this.props.Document._yMargin), + }} + ref={this.createDashEventsTarget}> {this.contents}
); } - -} \ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index f1d8123da..381436a56 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -118,7 +118,7 @@ export class DocumentContentsView extends React.Component< FormattedTextBoxProps & { isSelected: (outsideReaction: boolean) => boolean; select: (ctrl: boolean) => void; - scaling?: () => number; + NativeDimScaling?: () => number; setHeight?: (height: number) => void; layoutKey: string; } @@ -161,7 +161,6 @@ export class DocumentContentsView extends React.Component< 'LayoutTemplateString', 'LayoutTemplate', 'dontCenter', - 'ContentScaling', 'contextMenuItems', 'onClick', 'onDoubleClick', @@ -195,7 +194,7 @@ export class DocumentContentsView extends React.Component< // replace HTML with corresponding HTML tag as in: becomes const replacer2 = (match: any, p1: string, offset: any, string: any) => { - return ` number; + scaling?: () => number; // how uch doc is scaled so that link buttons can invert it } @observer export class DocumentLinksButton extends React.Component { @@ -42,53 +42,71 @@ export class DocumentLinksButton extends React.Component; public static invisibleWebRef = React.createRef(); - @action @undoBatch + @action + @undoBatch onLinkButtonMoved = (e: PointerEvent) => { if (this.props.InMenu && this.props.StartLink) { if (this._linkButton.current !== null) { - const linkDrag = UndoManager.StartBatch("Drag Link"); - this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, { - dragComplete: dropEv => { - if (this.props.View && dropEv.linkDocument) {// dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop - !dropEv.linkDocument.linkRelationship && (Doc.GetProto(dropEv.linkDocument).linkRelationship = "hyperlink"); - } - linkDrag?.end(); - }, - hideSource: false - }); + const linkDrag = UndoManager.StartBatch('Drag Link'); + this.props.View && + DragManager.StartLinkDrag(this._linkButton.current, this.props.View, this.props.View.ComponentView?.getAnchor, e.pageX, e.pageY, { + dragComplete: dropEv => { + if (this.props.View && dropEv.linkDocument) { + // dropEv.linkDocument equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop + !dropEv.linkDocument.linkRelationship && (Doc.GetProto(dropEv.linkDocument).linkRelationship = 'hyperlink'); + } + linkDrag?.end(); + }, + hideSource: false, + }); return true; } return false; } return false; - } + }; onLinkMenuOpen = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => { - if (doubleTap) { - DocumentView.showBackLinks(this.props.View.rootDoc); - } - }), undefined, undefined, - action(() => DocumentLinksButton.LinkEditorDocView = this.props.View)); - } + setupMoveUpEvents( + this, + e, + this.onLinkButtonMoved, + emptyFunction, + action((e, doubleTap) => { + if (doubleTap) { + DocumentView.showBackLinks(this.props.View.rootDoc); + } + }), + undefined, + undefined, + action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View)) + ); + }; @undoBatch onLinkButtonDown = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => { - if (doubleTap && this.props.InMenu && this.props.StartLink) { - //action(() => Doc.BrushDoc(this.props.View.Document)); - if (DocumentLinksButton.StartLink === this.props.View.props.Document) { - DocumentLinksButton.StartLink = undefined; - DocumentLinksButton.StartLinkView = undefined; - } else { - DocumentLinksButton.StartLink = this.props.View.props.Document; - DocumentLinksButton.StartLinkView = this.props.View; + setupMoveUpEvents( + this, + e, + this.onLinkButtonMoved, + emptyFunction, + action((e, doubleTap) => { + if (doubleTap && this.props.InMenu && this.props.StartLink) { + //action(() => Doc.BrushDoc(this.props.View.Document)); + if (DocumentLinksButton.StartLink === this.props.View.props.Document) { + DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; + } else { + DocumentLinksButton.StartLink = this.props.View.props.Document; + DocumentLinksButton.StartLinkView = this.props.View; + } } - } - })); - } + }) + ); + }; - @action @undoBatch + @action + @undoBatch onLinkClick = (e: React.MouseEvent): void => { if (this.props.InMenu && this.props.StartLink) { DocumentLinksButton.AnnotationId = undefined; @@ -96,108 +114,125 @@ export class DocumentLinksButton extends React.Component Doc.BrushDoc(this.props.View.Document)); } - } - + }; completeLink = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action((e, doubleTap) => { - if (doubleTap && !this.props.StartLink) { - if (DocumentLinksButton.StartLink === this.props.View.props.Document) { - DocumentLinksButton.StartLink = undefined; - DocumentLinksButton.StartLinkView = undefined; - DocumentLinksButton.AnnotationId = undefined; - } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) { - const sourceDoc = DocumentLinksButton.StartLink; - const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document; - const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, "links"); //why is long drag here when this is used for completing links by clicking? + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch( + action((e, doubleTap) => { + if (doubleTap && !this.props.StartLink) { + if (DocumentLinksButton.StartLink === this.props.View.props.Document) { + DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; + DocumentLinksButton.AnnotationId = undefined; + } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) { + const sourceDoc = DocumentLinksButton.StartLink; + const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document; + const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, 'links'); //why is long drag here when this is used for completing links by clicking? - LinkManager.currentLink = linkDoc; + LinkManager.currentLink = linkDoc; - runInAction(() => { - if (linkDoc) { - TaskCompletionBox.textDisplayed = "Link Created"; - TaskCompletionBox.popupX = e.screenX; - TaskCompletionBox.popupY = e.screenY - 133; - TaskCompletionBox.taskCompleted = true; + runInAction(() => { + if (linkDoc) { + TaskCompletionBox.textDisplayed = 'Link Created'; + TaskCompletionBox.popupX = e.screenX; + TaskCompletionBox.popupY = e.screenY - 133; + TaskCompletionBox.taskCompleted = true; - LinkDescriptionPopup.popupX = e.screenX; - LinkDescriptionPopup.popupY = e.screenY - 100; - LinkDescriptionPopup.descriptionPopup = true; + LinkDescriptionPopup.popupX = e.screenX; + LinkDescriptionPopup.popupY = e.screenY - 100; + LinkDescriptionPopup.descriptionPopup = true; - const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.popupX + 200 > rect.width) { - LinkDescriptionPopup.popupX -= 190; - TaskCompletionBox.popupX -= 40; - } - if (LinkDescriptionPopup.popupY + 100 > rect.height) { - LinkDescriptionPopup.popupY -= 40; - TaskCompletionBox.popupY -= 40; - } + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.popupX + 200 > rect.width) { + LinkDescriptionPopup.popupX -= 190; + TaskCompletionBox.popupX -= 40; + } + if (LinkDescriptionPopup.popupY + 100 > rect.height) { + LinkDescriptionPopup.popupY -= 40; + TaskCompletionBox.popupY -= 40; + } - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500); + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2500 + ); + } + }); } - }); - } - } - }))); - } + } + }) + ) + ); + }; - public static finishLinkClick = undoBatch(action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView,) => { - if (startLink === endLink) { - DocumentLinksButton.StartLink = undefined; - DocumentLinksButton.StartLinkView = undefined; - DocumentLinksButton.AnnotationId = undefined; - DocumentLinksButton.AnnotationUri = undefined; - //!this.props.StartLink - } else if (startLink !== endLink) { - endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink; - startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink; - const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, - DocumentLinksButton.AnnotationId ? "hypothes.is annotation" : undefined, undefined, undefined, true); + public static finishLinkClick = undoBatch( + action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView) => { + if (startLink === endLink) { + DocumentLinksButton.StartLink = undefined; + DocumentLinksButton.StartLinkView = undefined; + DocumentLinksButton.AnnotationId = undefined; + DocumentLinksButton.AnnotationUri = undefined; + //!this.props.StartLink + } else if (startLink !== endLink) { + endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink; + startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink; + const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined, undefined, undefined, true); - LinkManager.currentLink = linkDoc; + LinkManager.currentLink = linkDoc; - if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation - Doc.GetProto(linkDoc as Doc).linksToAnnotation = true; - Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId; - Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri; - const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink); - Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, - (startIsAnnotation ? startLink : endLink)); // edit annotation to add a Dash hyperlink to the linked doc - } + if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { + // if linking from a Hypothes.is annotation + Doc.GetProto(linkDoc as Doc).linksToAnnotation = true; + Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId; + Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri; + const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink); + Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc + } - if (linkDoc) { - TaskCompletionBox.textDisplayed = "Link Created"; - TaskCompletionBox.popupX = screenX; - TaskCompletionBox.popupY = screenY - 133; - TaskCompletionBox.taskCompleted = true; + if (linkDoc) { + TaskCompletionBox.textDisplayed = 'Link Created'; + TaskCompletionBox.popupX = screenX; + TaskCompletionBox.popupY = screenY - 133; + TaskCompletionBox.taskCompleted = true; - if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) { - LinkDescriptionPopup.popupX = screenX; - LinkDescriptionPopup.popupY = screenY - 100; - LinkDescriptionPopup.descriptionPopup = true; - } + if (LinkDescriptionPopup.showDescriptions === 'ON' || !LinkDescriptionPopup.showDescriptions) { + LinkDescriptionPopup.popupX = screenX; + LinkDescriptionPopup.popupY = screenY - 100; + LinkDescriptionPopup.descriptionPopup = true; + } - const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.popupX + 200 > rect.width) { - LinkDescriptionPopup.popupX -= 190; - TaskCompletionBox.popupX -= 40; - } - if (LinkDescriptionPopup.popupY + 100 > rect.height) { - LinkDescriptionPopup.popupY -= 40; - TaskCompletionBox.popupY -= 40; - } + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.popupX + 200 > rect.width) { + LinkDescriptionPopup.popupX -= 190; + TaskCompletionBox.popupX -= 40; + } + if (LinkDescriptionPopup.popupY + 100 > rect.height) { + LinkDescriptionPopup.popupY -= 40; + TaskCompletionBox.popupY -= 40; + } - setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500); + setTimeout( + action(() => { + TaskCompletionBox.taskCompleted = false; + }), + 2500 + ); + } } - } - })); + }) + ); @action clearLinks() { DocumentLinksButton.StartLink = undefined; @@ -208,9 +243,7 @@ export class DocumentLinksButton extends React.Component(this.props.View.allLinks)).forEach(link => { - if (DocUtils.FilterDocs([link], filters, []).length || - DocUtils.FilterDocs([link.anchor2 as Doc], filters, []).length || - DocUtils.FilterDocs([link.anchor1 as Doc], filters, []).length) { + if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.anchor2 as Doc], filters, []).length || DocUtils.FilterDocs([link.anchor1 as Doc], filters, []).length) { results.push(link); } }); @@ -219,48 +252,45 @@ export class DocumentLinksButton extends React.Component; - const isActive = (DocumentLinksButton.StartLink === this.props.View.props.Document) && this.props.StartLink; - return (!this.props.InMenu ? -
-
; + const isActive = DocumentLinksButton.StartLink === this.props.View.props.Document && this.props.StartLink; + return !this.props.InMenu ? ( +
+
{Array.from(this.filteredLinks).length}
- : -
- {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node -
+ {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? ( //if the origin node is not this node +
DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}>
- : (null) - } - { - this.props.InMenu && this.props.StartLink ? //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again -
- -
- : - (null) - } + ) : null} + {this.props.InMenu && this.props.StartLink ? ( //if link has been started from current node, then set behavior of link button to deactivate linking when clicked again +
+ +
+ ) : null}
); } @@ -268,25 +298,23 @@ export class DocumentLinksButton extends React.Component - { - (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || - (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? - {title}
}> - {this.linkButtonInner} - - : this.linkButtonInner - } -
; + transform: this.props.InMenu ? undefined : `scale(${this.props.scaling})`, + }}> + {(this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? ( + {title}
}>{this.linkButtonInner} + ) : ( + this.linkButtonInner + )} +
+ ); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3a8552325..6bf0c365a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -175,7 +175,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps { LayoutTemplateString?: string; dontCenter?: 'x' | 'y' | 'xy'; dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling - ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NativeWidth?: () => number; NativeHeight?: () => number; LayoutTemplate?: () => Opt; @@ -192,6 +191,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps { export interface DocumentViewInternalProps extends DocumentViewProps { NativeWidth: () => number; NativeHeight: () => number; + NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps isSelected: (outsideReaction?: boolean) => boolean; select: (ctrlPressed: boolean) => void; DocumentView: () => DocumentView; @@ -235,8 +235,8 @@ export class DocumentViewInternal extends DocComponent; } - @computed get ContentScale() { - return this.props.ContentScaling?.() || 1; + @computed get NativeDimScaling() { + return this.props.NativeDimScaling?.() || 1; } @computed get thumb() { return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url.href.replace('.png', '_m.png'); @@ -428,7 +428,7 @@ export class DocumentViewInternal extends DocComponent 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); - contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; setHeight = (height: number) => (this.layoutDoc._height = height); setContentView = action((view: { getAnchor?: () => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); @@ -989,6 +988,7 @@ export class DocumentViewInternal extends DocComponent (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale; @computed get contents() { TraceMobx(); const audioView = !this.layoutDoc._showAudio ? null : ( @@ -1041,7 +1041,7 @@ export class DocumentViewInternal extends DocComponent )} @@ -1503,7 +1503,7 @@ export class DocumentView extends React.Component { return this.props.dontCenter?.includes('y') ? 0 : this.Yshift; } - toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); + toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight()); focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); getBounds = () => { if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { @@ -1564,7 +1564,7 @@ export class DocumentView extends React.Component { NativeHeight = () => this.effectiveNativeHeight; PanelWidth = () => this.panelWidth; PanelHeight = () => this.panelHeight; - ContentScale = () => this.nativeScaling; + NativeDimScaling = () => this.nativeScaling; selfView = () => this; screenToLocalTransform = () => this.props @@ -1620,9 +1620,9 @@ export class DocumentView extends React.Component { PanelHeight={this.PanelHeight} NativeWidth={this.NativeWidth} NativeHeight={this.NativeHeight} + NativeDimScaling={this.NativeDimScaling} isSelected={this.isSelected} select={this.select} - ContentScaling={this.ContentScale} ScreenToLocalTransform={this.screenToLocalTransform} focus={this.props.focus || emptyFunction} ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index c170f9867..a714518cc 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -12,11 +12,12 @@ import { LightboxView } from '../LightboxView'; import './EquationBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; - @observer export class EquationBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); } - public static SelectOnLoad: string = ""; + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(EquationBox, fieldKey); + } + public static SelectOnLoad: string = ''; _ref: React.RefObject = React.createRef(); componentDidMount() { if (EquationBox.SelectOnLoad === this.rootDoc[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { @@ -25,75 +26,82 @@ export class EquationBox extends ViewBoxBaseComponent() { this._ref.current!.mathField.focus(); this._ref.current!.mathField.select(); } - reaction(() => StrCast(this.dataDoc.text), + reaction( + () => StrCast(this.dataDoc.text), text => { if (text && text !== this._ref.current!.mathField.latex()) { this._ref.current!.mathField.latex(text); } - }); - reaction(() => this.props.isSelected(), + } + ); + reaction( + () => this.props.isSelected(), selected => { if (this._ref.current) { - if (selected) this._ref.current.element.current.children[0].addEventListener("keydown", this.keyPressed, true); - else this._ref.current.element.current.children[0].removeEventListener("keydown", this.keyPressed); + if (selected) this._ref.current.element.current.children[0].addEventListener('keydown', this.keyPressed, true); + else this._ref.current.element.current.children[0].removeEventListener('keydown', this.keyPressed); } - }, { fireImmediately: true }); + }, + { fireImmediately: true } + ); } plot: any; @action keyPressed = (e: KeyboardEvent) => { - const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace("px", "")); - const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace("px", "")); - if (e.key === "Enter") { + const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace('px', '')); + const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace('px', '')); + if (e.key === 'Enter') { const nextEq = Docs.Create.EquationDocument({ - title: "# math", text: StrCast(this.dataDoc.text), _width, _height: 25, - x: NumCast(this.layoutDoc.x), y: NumCast(this.layoutDoc.y) + _height + 10 + title: '# math', + text: StrCast(this.dataDoc.text), + _width, + _height: 25, + x: NumCast(this.layoutDoc.x), + y: NumCast(this.layoutDoc.y) + _height + 10, }); EquationBox.SelectOnLoad = nextEq[Id]; this.props.addDocument?.(nextEq); e.stopPropagation(); - } - if (e.key === "Tab") { + if (e.key === 'Tab') { const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], { x: NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym](), y: NumCast(this.layoutDoc.y), - _width: 400, _height: 300, backgroundColor: "white" + _width: 400, + _height: 300, + backgroundColor: 'white', }); this.props.addDocument?.(graph); e.stopPropagation(); } - if (e.key === "Backspace" && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc); - } + if (e.key === 'Backspace' && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc); + }; onChange = (str: string) => { this.dataDoc.text = str; const style = this._ref.current && getComputedStyle(this._ref.current.element.current); if (style) { - const _height = Number(style.height.replace("px", "")); - const _width = Number(style.width.replace("px", "")); + const _height = Number(style.height.replace('px', '')); + const _width = Number(style.width.replace('px', '')); this.layoutDoc._width = Math.max(35, _width); this.layoutDoc._height = Math.max(25, _height); } - } + }; render() { TraceMobx(); - 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()} - > - -
); + const scale = (this.props.NativeDimScaling?.() || 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()}> + +
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 67cf18d8b..5a6c49809 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,18 +1,18 @@ -import React = require("react"); -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import { DateField } from "../../../fields/DateField"; -import { Doc, Field, FieldResult, Opt } from "../../../fields/Doc"; -import { List } from "../../../fields/List"; -import { WebField } from "../../../fields/URLField"; -import { DocumentView, DocumentViewSharedProps } from "./DocumentView"; -import { ScriptField } from "../../../fields/ScriptField"; -import { RecordingBox } from "./RecordingBox"; +import React = require('react'); +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import { DateField } from '../../../fields/DateField'; +import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; +import { WebField } from '../../../fields/URLField'; +import { DocumentView, DocumentViewSharedProps } from './DocumentView'; +import { ScriptField } from '../../../fields/ScriptField'; +import { RecordingBox } from './RecordingBox'; // // these properties get assigned through the render() method of the DocumentView when it creates this node. // However, that only happens because the properties are "defined" in the markup for the field view. -// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc. +// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc. // export interface FieldViewProps extends DocumentViewSharedProps { // FieldView specific props that are not part of DocumentView props @@ -23,10 +23,10 @@ export interface FieldViewProps extends DocumentViewSharedProps { isContentActive: (outsideReaction?: boolean) => boolean | undefined; isDocumentActive?: () => boolean; isSelected: (outsideReaction?: boolean) => boolean; - scaling?: () => number; setHeight?: (height: number) => void; - onBrowseClick?: () => (ScriptField | undefined); - onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined); + NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps + onBrowseClick?: () => ScriptField | undefined; + onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) pointerEvents?: () => Opt; @@ -42,7 +42,7 @@ export interface FieldViewProps extends DocumentViewSharedProps { @observer export class FieldView extends React.Component { public static LayoutString(fieldType: { name: string }, fieldStr: string) { - return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "" + return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "" } @computed @@ -75,23 +75,22 @@ export class FieldView extends React.Component { //} else if (field instanceof DateField) { return

{field.date.toLocaleString()}

; - } - else if (field instanceof Doc) { - return

{field.title?.toString()}

; - } - else if (field instanceof List) { - return
{field.length ? field.map(f => Field.toString(f)).join(", ") : ""}
; + } else if (field instanceof Doc) { + return ( +

+ {field.title?.toString()} +

+ ); + } else if (field instanceof List) { + return
{field.length ? field.map(f => Field.toString(f)).join(', ') : ''}
; } // bcz: this belongs here, but it doesn't render well so taking it out for now else if (field instanceof WebField) { return

{Field.toString(field.url.href)}

; - } - else if (!(field instanceof Promise)) { + } else if (!(field instanceof Promise)) { return

{Field.toString(field)}

; - } - else { - return

{"Waiting for server..."}

; + } else { + return

{'Waiting for server...'}

; } } - -} \ No newline at end of file +} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index ffa839fcb..9590bcb15 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -148,8 +148,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent { const localDelta = this.props .ScreenToLocalTransform() - .scale(this.props.scaling?.() || 1) + .scale(this.props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); const fullWidth = this.layoutDoc[WidthSym](); const mapWidth = fullWidth - this.sidebarWidth(); @@ -559,8 +559,8 @@ export class MapBox extends ViewBoxAnnotatableComponent this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (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); + panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); + panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 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)); transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; @@ -589,13 +589,12 @@ export class MapBox extends ViewBoxAnnotatableComponent; return (
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index 72569135b..630ae18f5 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -62,7 +62,7 @@ export class MapBoxInfoWindow extends React.Component { const localDelta = this.props .ScreenToLocalTransform() - .scale(this.props.scaling?.() || 1) + .scale(this.props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.scaling?.() || 1)) / nativeWidth; + const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth; if (ratio >= 1) { this.layoutDoc.nativeWidth = nativeWidth * ratio; onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]); @@ -433,12 +433,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent 1; isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected(); @computed get renderPdfView() { TraceMobx(); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; - const scale = previewScale * (this.props.scaling?.() || 1); + const scale = previewScale * (this.props.NativeDimScaling?.() || 1); return (
[this.youtubeVideoId ? this.youtubeContent : this.content]; - scaling = () => this.props.scaling?.() || 1; + scaling = () => this.props.NativeDimScaling?.() || 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); @@ -911,7 +911,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent ((this.props.scaling?.() || 1) * this.heightPercent) / 100; + marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100; marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0]; timelineDocFilter = () => [`_timelineLabel:true,${Utils.noRecursionHack}:x`]; @@ -1124,7 +1124,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { if (autoHeight) { this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']); - this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.scaling?.() || 1)); + this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.NativeDimScaling?.() || 1)); } } ); @@ -276,7 +276,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); @@ -322,7 +322,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { const sel = this._iframe?.contentWindow?.getSelection?.(); const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!); - const scale = (this.props.scaling?.() || 1) * mainContBounds.scale; + const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale; const word = getWordAtPoint(e.target, e.clientX, e.clientY); this._setPreviewCursor?.(e.clientX, e.clientY, false, true); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); @@ -604,7 +604,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { - const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); + const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1); this.layoutDoc.forceReflow = !nw; if (nw) { Doc.SetInPlace(this.layoutDoc, this.fieldKey + '-nativeWidth', nw, true); @@ -717,12 +717,12 @@ export class WebBox extends ViewBoxAnnotatableComponent= 1) { this.layoutDoc.nativeWidth = nativeWidth * ratio; this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio); @@ -834,8 +834,8 @@ export class WebBox extends ViewBoxAnnotatableComponent) => (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() + WebBox.sidebarResizerWidth; // (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); + panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); + panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 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; basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')]; @@ -852,7 +852,7 @@ export class WebBox extends ViewBoxAnnotatableComponent string[]) => ( this.props.Document._fitContentsToBox; - sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); // console.log("printting allSideBarDocs"); @@ -1705,7 +1705,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.props .ScreenToLocalTransform() - .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0) + .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0) .scale(1 / NumCast(this.layoutDoc._viewScale, 1)); @computed get audioHandle() { @@ -1768,7 +1768,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent() { } }; - headerUp = (e: React.PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); - }; - /** * Function to drag and drop the pres element to a diferent location */ @@ -477,8 +472,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { })} onPointerOver={this.onPointerOver} onPointerLeave={this.onPointerLeave} - onPointerDown={this.headerDown} - onPointerUp={this.headerUp}> + onPointerDown={this.headerDown}> {/* {miniView ? // when width is LESS than 110 px
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 837734edf..2c83082b7 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -41,7 +41,6 @@ interface IViewerProps extends FieldViewProps { url: string; loaded?: (nw: number, nh: number, np: number) => void; setPdfViewer: (view: PDFViewer) => void; - ContentScaling?: () => number; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined; } @@ -97,7 +96,7 @@ export class PDFViewer extends React.Component { autoHeight => { if (autoHeight) { this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']); - this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.scaling?.() || 1)); + this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + '-nativeHeight']) * (this.props.NativeDimScaling?.() || 1)); } } ); @@ -162,7 +161,7 @@ export class PDFViewer extends React.Component { const mainCont = this._mainCont.current; let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { - const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1); + const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { focusSpeed = 500; @@ -486,8 +485,8 @@ export class PDFViewer extends React.Component { showInfo = action((anno: Opt) => (this._overlayAnnoInfo = anno)); overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1)); - 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); + panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); + panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')]; transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; @@ -509,7 +508,6 @@ export class PDFViewer extends React.Component { PanelWidth={this.panelWidth} dropAction={'alias'} select={emptyFunction} - ContentScaling={this.contentZoom} bringToFront={emptyFunction} docFilters={docFilters || this.basicFilter} styleProvider={this.childStyleProvider} @@ -556,10 +554,6 @@ export class PDFViewer extends React.Component { @computed get pdfViewerDiv() { return
; } - @computed get contentScaling() { - return this.props.ContentScaling?.() || 1; - } - contentZoom = () => NumCast(this.props.layoutDoc._viewScale, 1); savedAnnotations = () => this._savedAnnotations; render() { TraceMobx(); @@ -574,8 +568,7 @@ export class PDFViewer extends React.Component { onClick={this.onClick} style={{ overflowX: NumCast(this.props.layoutDoc._viewScale, 1) !== 1 ? 'scroll' : undefined, - height: !this.props.Document._fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`, - transform: `scale(${this.contentScaling})`, + height: !this.props.Document._fitWidth && window.screen.width > 600 ? Doc.NativeHeight(this.props.Document) : `100%`, }}> {this.pdfViewerDiv} {this.annotationLayer} -- cgit v1.2.3-70-g09d2 From 318a25826746a83d8d72ecfe6af650b46706a9c3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 25 Jul 2022 09:37:27 -0400 Subject: fixed equationBox width sizing --- src/client/views/nodes/EquationBox.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index a714518cc..0bd30bce9 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -91,11 +91,21 @@ export class EquationBox extends ViewBoxBaseComponent() { const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); return (
{ + r instanceof HTMLDivElement && + new ResizeObserver( + action((entries: any) => { + if (entries[0].contentBoxSize[0].inlineSize) { + this.rootDoc._width = entries[0].contentBoxSize[0].inlineSize; + } + }) + ).observe(r); + }} className="equationBox-cont" onPointerDown={e => !e.ctrlKey && e.stopPropagation()} style={{ transform: `scale(${scale})`, - width: `${100 / scale}%`, + width: 'fit-content', // `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: !this.props.isSelected() ? 'none' : undefined, }} -- cgit v1.2.3-70-g09d2 From 8757b03bfd6f3d33335f8e126163b1daa2376589 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 25 Jul 2022 10:55:07 -0400 Subject: made formatttedtextbox annotation handle work the same as pdf/web. prevent header from hiding annotation button handle. fixed resizing text boxes. --- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/MapBox/MapBox.tsx | 38 +- src/client/views/nodes/PDFBox.tsx | 33 +- src/client/views/nodes/WebBox.tsx | 29 +- .../nodes/formattedText/FormattedTextBox.scss | 553 +++++++++++++++++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 14 +- 7 files changed, 510 insertions(+), 160 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 964fd36c8..4d38a8194 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -521,7 +521,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } else doc._height = actualdH; } } else { - const maxHeight = Math.max(nheight, NumCast(doc.scrollHeight)) * docView.NativeDimScaling(); + const maxHeight = Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); dH && (doc._height = actualdH > maxHeight ? maxHeight : actualdH); dW && (doc._width = actualdW); dH && (doc._autoHeight = false); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6bf0c365a..7032abba2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1229,6 +1229,7 @@ export class DocumentViewInternal extends DocComponent d?.author).length; - const color = !annotated ? Colors.WHITE : Colors.BLACK; - const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props as any, StyleProp.WidgetColor + (annotated ? ':annotated' : '')); - return !annotated ? null : ( + return (
- + display: !this.props.isContentActive() ? 'none' : undefined, + top: StrCast(this.rootDoc._showTitle) === 'title' ? 20 : 5, + backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK, + }} + onPointerDown={this.sidebarBtnDown}> +
); } @@ -665,18 +660,7 @@ export class MapBox extends ViewBoxAnnotatableComponent
-
- -
+ {this.sidebarHandle}
); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index fad22d6e9..42df2ac5d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -8,7 +8,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; +import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { KeyCodes } from '../../util/KeyCodes'; @@ -23,8 +23,8 @@ import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; -import { VideoBox } from './VideoBox'; import './PDFBox.scss'; +import { VideoBox } from './VideoBox'; import React = require('react'); @observer @@ -390,18 +390,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent {this._pageControls ? pageBtns : null}
-
this.sidebarBtnDown(e, true)}> - -
+ {this.sidebarHandle}
); } @@ -432,6 +421,22 @@ export class PDFBox extends ViewBoxAnnotatableComponent this.sidebarBtnDown(e, true)}> + +
+ ); + } isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected(); @computed get renderPdfView() { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 71d6959a3..55228c19f 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -741,6 +741,22 @@ export class WebBox extends ViewBoxAnnotatableComponent this.sidebarBtnDown(e, true)}> + +
+ ); + } @observable _previewNativeWidth: Opt = undefined; @observable _previewWidth: Opt = undefined; toggleSidebar = action((preview: boolean = false) => { @@ -955,18 +971,7 @@ export class WebBox extends ViewBoxAnnotatableComponent -
this.sidebarBtnDown(e, true)}> - -
+ {this.sidebarHandle} {!this.props.isContentActive() ? null : this.searchUI}
); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 27817f317..d3d8c47c0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -1,4 +1,4 @@ -@import "../../global/globalCssVariables"; +@import '../../global/globalCssVariables'; .ProseMirror { width: 100%; @@ -11,13 +11,13 @@ } audiotag { - left: 0; - position: absolute; - cursor: pointer; - border-radius: 10px; - width: 10px; - margin-top: -2px; - font-size: 4px; + left: 0; + position: absolute; + cursor: pointer; + border-radius: 10px; + width: 10px; + margin-top: -2px; + font-size: 4px; background: lightblue; } audiotag:hover { @@ -63,12 +63,11 @@ audiotag:hover { .formattedTextBox-outer-selected { cursor: text; } - + .formattedTextBox-sidebar-handle { position: absolute; top: 0; - left: 17px; - //top: calc(50% - 17.5px); // use this to center vertically -- make sure it looks okay for slide views + right: 0; width: 17px; height: 17px; font-size: 11px; @@ -79,15 +78,14 @@ audiotag:hover { display: flex; justify-content: center; align-items: center; - cursor:grabbing; + cursor: grabbing; box-shadow: $standard-box-shadow; // transition: 0.2s; opacity: 0.3; - &:hover{ + &:hover { opacity: 1 !important; filter: brightness(0.85); } - } .formattedTextBox-sidebar, @@ -117,14 +115,15 @@ audiotag:hover { left: 10%; } -.formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected, -.formattedTextBox-inner, +.formattedTextBox-inner-rounded, +.formattedTextBox-inner-rounded-selected, +.formattedTextBox-inner, .formattedTextBox-inner-minimal, .formattedTextBox-inner-selected { height: 100%; white-space: pre-wrap; .ProseMirror:hover { - background: rgba(200,200,200,0.2); + background: rgba(200, 200, 200, 0.2); } hr { display: block; @@ -141,7 +140,7 @@ audiotag:hover { .formattedTextBox-inner-rounded-selected, .formattedTextBox-inner-selected { > .ProseMirror { - padding:10px; + padding: 10px; } } .formattedTextBox-outer-selected { @@ -236,18 +235,17 @@ footnote::after { position: absolute; top: -5px; left: 27px; - content: " "; + content: ' '; height: 0; width: 0; } - .formattedTextBox-inlineComment { position: relative; width: 40px; height: 20px; &::before { - content: "→"; + content: '→'; } &:hover { background: orange; @@ -260,7 +258,7 @@ footnote::after { width: 40px; height: 20px; &::after { - content: "←"; + content: '←'; } } @@ -270,21 +268,21 @@ footnote::after { width: 40px; height: 20px; &::after { - content: "..."; + content: '...'; } } .prosemirror-anchor { - overflow:hidden; - display:inline-grid; + overflow: hidden; + display: inline-grid; } .prosemirror-linkBtn { - background:unset; - color:unset; - padding:0; + background: unset; + color: unset; + padding: 0; text-transform: unset; letter-spacing: unset; - font-size:unset; + font-size: unset; } .prosemirror-links { display: none; @@ -294,28 +292,28 @@ footnote::after { z-index: 1; padding: 5; border-radius: 2px; - } - .prosemirror-hrefoptions{ - width:0px; - border:unset; - padding:0px; - } - - .prosemirror-links a { +} +.prosemirror-hrefoptions { + width: 0px; + border: unset; + padding: 0px; +} + +.prosemirror-links a { float: left; color: white; text-decoration: none; border-radius: 3px; - } +} - .prosemirror-links a:hover { +.prosemirror-links a:hover { background-color: #eee; color: black; - } +} - .prosemirror-anchor:hover .prosemirror-links { +.prosemirror-anchor:hover .prosemirror-links { display: grid; - } +} .ProseMirror { padding: 0px; @@ -334,7 +332,8 @@ footnote::after { border-left: solid 2px dimgray; } - ol, ul { + ol, + ul { counter-reset: deci1 0 multi1 0; padding-left: 1em; font-family: inherit; @@ -342,42 +341,231 @@ footnote::after { ol { font-family: inherit; } - .bullet { p { font-family: inherit} margin-left: 0; } - .bullet1 { p { font-family: inherit} } - .bullet2,.bullet3,.bullet4,.bullet5,.bullet6 { p { font-family: inherit} font-size: smaller; } + .bullet { + p { + font-family: inherit; + } + margin-left: 0; + } + .bullet1 { + p { + font-family: inherit; + } + } + .bullet2, + .bullet3, + .bullet4, + .bullet5, + .bullet6 { + p { + font-family: inherit; + } + font-size: smaller; + } - .decimal1-ol { counter-reset: deci1; p {display: inline-block; font-family: inherit} margin-left: 0; } - .decimal2-ol { counter-reset: deci2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.1em;} - .decimal3-ol { counter-reset: deci3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;} - .decimal4-ol { counter-reset: deci4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;} - .decimal5-ol { counter-reset: deci5; p {display: inline-block; font-family: inherit} font-size: smaller; } - .decimal6-ol { counter-reset: deci6; p {display: inline-block; font-family: inherit} font-size: smaller; } - .decimal7-ol { counter-reset: deci7; p {display: inline-block; font-family: inherit} font-size: smaller; } + .decimal1-ol { + counter-reset: deci1; + p { + display: inline-block; + font-family: inherit; + } + margin-left: 0; + } + .decimal2-ol { + counter-reset: deci2; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 2.1em; + } + .decimal3-ol { + counter-reset: deci3; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 2.85em; + } + .decimal4-ol { + counter-reset: deci4; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 3.85em; + } + .decimal5-ol { + counter-reset: deci5; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + } + .decimal6-ol { + counter-reset: deci6; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + } + .decimal7-ol { + counter-reset: deci7; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + } - .multi1-ol { counter-reset: multi1; p {display: inline-block; font-family: inherit} margin-left: 0; padding-left: 1.2em } - .multi2-ol { counter-reset: multi2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;} - .multi3-ol { counter-reset: multi3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;} - .multi4-ol { counter-reset: multi4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;} + .multi1-ol { + counter-reset: multi1; + p { + display: inline-block; + font-family: inherit; + } + margin-left: 0; + padding-left: 1.2em; + } + .multi2-ol { + counter-reset: multi2; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 2em; + } + .multi3-ol { + counter-reset: multi3; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 2.85em; + } + .multi4-ol { + counter-reset: multi4; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 3.85em; + } //.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " } - .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } - .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; vertical-align: top; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } - .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; vertical-align: top; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } - .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; vertical-align: top; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } - .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; vertical-align: top; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } - .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; vertical-align: top; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } - .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; vertical-align: top; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } + .decimal1:before { + transition: 0.5s; + counter-increment: deci1; + display: inline-block; + vertical-align: top; + margin-left: -1em; + width: 1em; + content: counter(deci1) '. '; + } + .decimal2:before { + transition: 0.5s; + counter-increment: deci2; + display: inline-block; + vertical-align: top; + margin-left: -2.1em; + width: 2.1em; + content: counter(deci1) '.' counter(deci2) '. '; + } + .decimal3:before { + transition: 0.5s; + counter-increment: deci3; + display: inline-block; + vertical-align: top; + margin-left: -2.85em; + width: 2.85em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '. '; + } + .decimal4:before { + transition: 0.5s; + counter-increment: deci4; + display: inline-block; + vertical-align: top; + margin-left: -3.85em; + width: 3.85em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '. '; + } + .decimal5:before { + transition: 0.5s; + counter-increment: deci5; + display: inline-block; + vertical-align: top; + margin-left: -2em; + width: 5em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '. '; + } + .decimal6:before { + transition: 0.5s; + counter-increment: deci6; + display: inline-block; + vertical-align: top; + margin-left: -2em; + width: 6em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '.' counter(deci6) '. '; + } + .decimal7:before { + transition: 0.5s; + counter-increment: deci7; + display: inline-block; + vertical-align: top; + margin-left: -2em; + width: 7em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '.' counter(deci6) '.' counter(deci7) '. '; + } - .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; vertical-align: top; margin-left: -1.3em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } - .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; vertical-align: top; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } - .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; vertical-align: top; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } - .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; vertical-align: top; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } + .multi1:before { + transition: 0.5s; + counter-increment: multi1; + display: inline-block; + vertical-align: top; + margin-left: -1.3em; + width: 1.2em; + content: counter(multi1, upper-alpha) '. '; + } + .multi2:before { + transition: 0.5s; + counter-increment: multi2; + display: inline-block; + vertical-align: top; + margin-left: -2em; + width: 2em; + content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '. '; + } + .multi3:before { + transition: 0.5s; + counter-increment: multi3; + display: inline-block; + vertical-align: top; + margin-left: -2.85em; + width: 2.85em; + content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '. '; + } + .multi4:before { + transition: 0.5s; + counter-increment: multi4; + display: inline-block; + vertical-align: top; + margin-left: -4.2em; + width: 4.2em; + content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '.' counter(multi4, lower-roman) '. '; + } } - @media only screen and (max-width: 1000px) { - @import "../../global/globalCssVariables"; + @import '../../global/globalCssVariables'; .ProseMirror { width: 100%; @@ -425,7 +613,7 @@ footnote::after { width: 100%; height: 100%; } - + .formattedTextBox-sidebar-handle { position: absolute; background: lightgray; @@ -562,18 +750,17 @@ footnote::after { position: absolute; top: -5px; left: 27px; - content: " "; + content: ' '; height: 0; width: 0; } - .formattedTextBox-inlineComment { position: relative; width: 40px; height: 20px; &::before { - content: "→"; + content: '→'; } &:hover { background: orange; @@ -586,7 +773,7 @@ footnote::after { width: 40px; height: 20px; &::after { - content: "←"; + content: '←'; } } @@ -596,7 +783,7 @@ footnote::after { width: 40px; height: 20px; &::after { - content: "..."; + content: '...'; } } @@ -606,7 +793,8 @@ footnote::after { font-family: inherit; } - ol, ul { + ol, + ul { counter-reset: deci1 0 multi1 0; padding-left: 1em; font-family: inherit; @@ -616,30 +804,191 @@ footnote::after { font-family: inherit; } - .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; } - .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;} - .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} - .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;} - .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; } - .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; } - .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; } - - .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em } - .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;} - .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} - .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;} - - .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } - .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } - .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } - .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } - .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } - .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } - .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } - - .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } - .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } - .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } - .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } + .decimal1-ol { + counter-reset: deci1; + p { + display: inline; + font-family: inherit; + } + margin-left: 0; + } + .decimal2-ol { + counter-reset: deci2; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + padding-left: 1em; + } + .decimal3-ol { + counter-reset: deci3; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + padding-left: 2em; + } + .decimal4-ol { + counter-reset: deci4; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + padding-left: 3em; + } + .decimal5-ol { + counter-reset: deci5; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + } + .decimal6-ol { + counter-reset: deci6; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + } + .decimal7-ol { + counter-reset: deci7; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + } + + .multi1-ol { + counter-reset: multi1; + p { + display: inline; + font-family: inherit; + } + margin-left: 0; + padding-left: 1.2em; + } + .multi2-ol { + counter-reset: multi2; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + padding-left: 1.4em; + } + .multi3-ol { + counter-reset: multi3; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + padding-left: 2em; + } + .multi4-ol { + counter-reset: multi4; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + padding-left: 3.4em; + } + + .decimal1:before { + transition: 0.5s; + counter-increment: deci1; + display: inline-block; + margin-left: -1em; + width: 1em; + content: counter(deci1) '. '; + } + .decimal2:before { + transition: 0.5s; + counter-increment: deci2; + display: inline-block; + margin-left: -2.1em; + width: 2.1em; + content: counter(deci1) '.' counter(deci2) '. '; + } + .decimal3:before { + transition: 0.5s; + counter-increment: deci3; + display: inline-block; + margin-left: -2.85em; + width: 2.85em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '. '; + } + .decimal4:before { + transition: 0.5s; + counter-increment: deci4; + display: inline-block; + margin-left: -3.85em; + width: 3.85em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '. '; + } + .decimal5:before { + transition: 0.5s; + counter-increment: deci5; + display: inline-block; + margin-left: -2em; + width: 5em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '. '; + } + .decimal6:before { + transition: 0.5s; + counter-increment: deci6; + display: inline-block; + margin-left: -2em; + width: 6em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '.' counter(deci6) '. '; + } + .decimal7:before { + transition: 0.5s; + counter-increment: deci7; + display: inline-block; + margin-left: -2em; + width: 7em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '.' counter(deci5) '.' counter(deci6) '.' counter(deci7) '. '; + } + + .multi1:before { + transition: 0.5s; + counter-increment: multi1; + display: inline-block; + margin-left: -1em; + width: 1.2em; + content: counter(multi1, upper-alpha) '. '; + } + .multi2:before { + transition: 0.5s; + counter-increment: multi2; + display: inline-block; + margin-left: -2em; + width: 2em; + content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '. '; + } + .multi3:before { + transition: 0.5s; + counter-increment: multi3; + display: inline-block; + margin-left: -2.85em; + width: 2.85em; + content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '. '; + } + .multi4:before { + transition: 0.5s; + counter-increment: multi4; + display: inline-block; + margin-left: -4.2em; + width: 4.2em; + content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '.' counter(multi4, lower-roman) '. '; + } } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e9b048c5e..ead086aca 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -11,7 +11,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { DateField } from '../../../../fields/DateField'; -import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; @@ -61,6 +61,7 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); +import { text } from 'body-parser'; const translateGoogleApi = require('translate-google-api'); export interface FormattedTextBoxProps { @@ -654,8 +655,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const bounds = this._ref.current!.getBoundingClientRect(); - this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; + const localDelta = this.props + .ScreenToLocalTransform() + .scale(this.props.NativeDimScaling?.() || 1) + .transformDirection(delta[0], delta[1]); + const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100; + const width = this.layoutDoc[WidthSym]() + localDelta[0]; + this.layoutDoc._sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%'; + this.layoutDoc.width = width; this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%'; e.preventDefault(); return false; @@ -1726,7 +1733,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Tue, 26 Jul 2022 09:27:12 -0400 Subject: turned off resetriting text box resizing to max of scroll height. fixed placement/behavior of sidebar anno button for text. --- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/MainView.tsx | 1 + src/client/views/SidebarAnnos.tsx | 8 +++--- src/client/views/nodes/MapBox/MapBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 30 ++++++++++---------- src/client/views/nodes/WebBox.tsx | 32 ++++++++++++---------- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 ++++-- 7 files changed, 45 insertions(+), 38 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 922f037f4..432dd360a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -521,7 +521,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } else doc._height = actualdH; } } else { - const maxHeight = Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); + const maxHeight = 0; //Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH); dW && (doc._width = actualdW); dH && (doc._autoHeight = false); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7e032af5e..a85460137 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -159,6 +159,7 @@ export class MainView extends React.Component { 'viewTransition', 'panX', 'panY', + 'fitWidth', 'nativeWidth', 'nativeHeight', 'text-scrollHeight', diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 9fdf9d2be..1c14c7cd5 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -101,7 +101,7 @@ export class SidebarAnnos extends React.Component { !this.props.showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP - ? this.props.PanelWidth() + ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth); panelHeight = () => this.props.PanelHeight() - this.filtersHeight(); addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey); @@ -144,14 +144,14 @@ export class SidebarAnnos extends React.Component { top: this.props.rootDoc.type !== DocumentType.RTF && StrCast(this.props.rootDoc._showTitle) === 'title' ? 15 : 0, right: 0, background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor), - width: `${this.panelWidth()}px`, + width: `100%`, height: '100%', }}> -
e.stopPropagation()}> +
e.stopPropagation()}> {this.allUsers.map(renderUsers)} {this.allMetadata.map(renderMeta)}
-
+
{/* */} -
+
- +
+ +
{this.settingsPanel()}
); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 55228c19f..dd14822af 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -956,21 +956,23 @@ export class WebBox extends ViewBoxAnnotatableComponent this.sidebarBtnDown(e, false)} /> - +
+ +
{this.sidebarHandle} {!this.props.isContentActive() ? null : this.searchUI}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ead086aca..fe121471a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -124,6 +124,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
- {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} - {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} + {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} + {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} {!this.layoutDoc._showAudio ? null : this.audioHandle}
-- cgit v1.2.3-70-g09d2 From 2f9ed7522d0615dfc27b7382be8b66eb341722da Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 26 Jul 2022 11:16:39 -0400 Subject: made width a playground field so shared views can open annotation pane. made title shrink for annotation button only when title doesn't add a margin. Fixed closing annotation view in lightbox not to make docuent shrink to minimum size by using percents, not pixels. --- src/client/views/MainView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a85460137..4dc8a2e4a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -171,6 +171,7 @@ export class MainView extends React.Component { 'curPage', 'viewType', 'chromeHidden', + 'width', 'nativeWidth', ]); // can play with these fields on someone else's } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7032abba2..2ad8b8af0 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1229,7 +1229,7 @@ export class DocumentViewInternal extends DocComponent { - const prevWidth = this.sidebarWidth(); + const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', '')); if (preview) this._showSidebar = true; else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; - this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); + this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth); }; sidebarDown = (e: React.PointerEvent) => { const batch = UndoManager.StartBatch('sidebar'); -- cgit v1.2.3-70-g09d2 From 6ab111c7c4c2d2c0259f88d71781b618ddb2356e Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 26 Jul 2022 12:08:04 -0400 Subject: avoid crashes when urlfield is undefined. prevent multiple views from setting thumbnail for same doc at same time. prevent unregistered views from creating thumbnails. --- src/client/views/nodes/DocumentView.tsx | 4 +- src/client/views/nodes/WebBox.tsx | 13 +++++-- src/fields/URLField.ts | 69 ++++++++++++++++++++------------- 3 files changed, 54 insertions(+), 32 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2ad8b8af0..8847c0c6a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -239,7 +239,7 @@ export class DocumentViewInternal extends DocComponent { const imageBitmap = ImageCast(this.layoutDoc['thumb-frozen'])?.url.href; const scrollTop = NumCast(this.layoutDoc._scrollTop); const nativeWidth = NumCast(this.layoutDoc.nativeWidth); const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth(); - if (!this.lockout && this._iframe && !imageBitmap && (scrollTop !== this.layoutDoc.thumbScrollTop || nativeWidth !== this.layoutDoc.thumbNativeWidth || nativeHeight !== this.layoutDoc.thumbNativeHeight)) { + if ( + !this.rootDoc.thumbLockout && + !this.props.dontRegisterView && + this._iframe && + !imageBitmap && + (scrollTop !== this.layoutDoc.thumbScrollTop || nativeWidth !== this.layoutDoc.thumbNativeWidth || nativeHeight !== this.layoutDoc.thumbNativeHeight) + ) { var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument); if (!htmlString) { htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text(); } this.layoutDoc.thumb = undefined; - this.lockout = true; // lock to prevent multiple thumb updates. + this.rootDoc.thumbLockout = true; // lock to prevent multiple thumb updates. CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop) .then((data_url: any) => { VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename => setTimeout( action(() => { - this.lockout = false; + this.rootDoc.thumbLockout = false; this.layoutDoc.thumb = new ImageField(returnedfilename); this.layoutDoc.thumbScrollTop = scrollTop; this.layoutDoc.thumbNativeWidth = nativeWidth; diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts index 36dd56a1a..00c78e231 100644 --- a/src/fields/URLField.ts +++ b/src/fields/URLField.ts @@ -1,16 +1,14 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, custom } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { ToScriptString, ToString, Copy } from "./FieldSymbols"; -import { scriptingGlobal } from "../client/util/ScriptingGlobals"; -import { Utils } from "../Utils"; +import { Deserializable } from '../client/util/SerializationHelper'; +import { serializable, custom } from 'serializr'; +import { ObjectField } from './ObjectField'; +import { ToScriptString, ToString, Copy } from './FieldSymbols'; +import { scriptingGlobal } from '../client/util/ScriptingGlobals'; +import { Utils } from '../Utils'; function url() { return custom( function (value: URL) { - return value.origin === window.location.origin ? - value.pathname : - value.href; + return value?.origin === window.location.origin ? value.pathname : value?.href; }, function (jsonValue: string) { return new URL(jsonValue, window.location.origin); @@ -26,23 +24,23 @@ export abstract class URLField extends ObjectField { constructor(url: URL); constructor(url: URL | string) { super(); - if (typeof url === "string") { - url = url.startsWith("http") ? new URL(url) : new URL(url, window.location.origin); + if (typeof url === 'string') { + url = url.startsWith('http') ? new URL(url) : new URL(url, window.location.origin); } this.url = url; } [ToScriptString]() { - if (Utils.prepend(this.url.pathname) === this.url.href) { + if (Utils.prepend(this.url?.pathname) === this.url?.href) { return `new ${this.constructor.name}("${this.url.pathname}")`; } return `new ${this.constructor.name}("${this.url.href}")`; } [ToString]() { - if (Utils.prepend(this.url.pathname) === this.url.href) { + if (Utils.prepend(this.url?.pathname) === this.url?.href) { return this.url.pathname; } - return this.url.href; + return this.url?.href; } [Copy](): this { @@ -50,16 +48,35 @@ export abstract class URLField extends ObjectField { } } -export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short.ogg"; - -@scriptingGlobal @Deserializable("audio") export class AudioField extends URLField { } -@scriptingGlobal @Deserializable("recording") export class RecordingField extends URLField { } -@scriptingGlobal @Deserializable("image") export class ImageField extends URLField { } -@scriptingGlobal @Deserializable("video") export class VideoField extends URLField { } -@scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } -@scriptingGlobal @Deserializable("web") export class WebField extends URLField { } -@scriptingGlobal @Deserializable("map") export class MapField extends URLField { } -@scriptingGlobal @Deserializable("csv") export class CsvField extends URLField { } -@scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { } -@scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { } +export const nullAudio = 'https://actions.google.com/sounds/v1/alarms/beep_short.ogg'; +@scriptingGlobal +@Deserializable('audio') +export class AudioField extends URLField {} +@scriptingGlobal +@Deserializable('recording') +export class RecordingField extends URLField {} +@scriptingGlobal +@Deserializable('image') +export class ImageField extends URLField {} +@scriptingGlobal +@Deserializable('video') +export class VideoField extends URLField {} +@scriptingGlobal +@Deserializable('pdf') +export class PdfField extends URLField {} +@scriptingGlobal +@Deserializable('web') +export class WebField extends URLField {} +@scriptingGlobal +@Deserializable('map') +export class MapField extends URLField {} +@scriptingGlobal +@Deserializable('csv') +export class CsvField extends URLField {} +@scriptingGlobal +@Deserializable('youtube') +export class YoutubeField extends URLField {} +@scriptingGlobal +@Deserializable('webcam') +export class WebCamField extends URLField {} -- cgit v1.2.3-70-g09d2 From 5f8248acbc5b83a4e1571779913f9ed5700382de Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 26 Jul 2022 15:54:23 -0400 Subject: fixed clicking linkDocPreview to follow links. switched target drag behavior from title to preview --- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/LinkDocPreview.scss | 18 +++++++---- src/client/views/nodes/LinkDocPreview.tsx | 52 +++++++++++++++++++++--------- 3 files changed, 49 insertions(+), 23 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4dc8a2e4a..e96f65548 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -205,7 +205,7 @@ export class MainView extends React.Component { componentWillUnMount() { window.removeEventListener('keyup', KeyManager.Instance.unhandle); window.removeEventListener('keydown', KeyManager.Instance.handle); - window.removeEventListener('pointerdown', this.globalPointerDown); + window.removeEventListener('pointerdown', this.globalPointerDown, true); window.removeEventListener('paste', KeyManager.Instance.paste as any); document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener); } diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss index 3febbcecb..c68e55f73 100644 --- a/src/client/views/nodes/LinkDocPreview.scss +++ b/src/client/views/nodes/LinkDocPreview.scss @@ -8,6 +8,7 @@ border-bottom: 8px solid white; border-right: 8px solid white; z-index: 2004; + cursor: pointer; .linkDocPreview-inner { background-color: white; @@ -63,13 +64,16 @@ cursor: pointer; } } + } - .linkDocPreview-preview-wrapper { - overflow: hidden; - align-content: center; - justify-content: center; - background-color: rgb(160, 160, 160); - } + .linkDocPreview-preview-wrapper { + overflow: hidden; + align-content: center; + justify-content: center; + pointer-events: all; + background-color: rgb(160, 160, 160); + overflow: auto; + cursor: grab; } } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index d8b943ae4..a12564839 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react'; import wiki from 'wikijs'; import { Doc, DocCastAsync, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -36,6 +36,7 @@ export class LinkDocPreview extends React.Component { } _infoRef = React.createRef(); + _linkDocRef = React.createRef(); @observable public static LinkInfo: Opt; @observable _targetDoc: Opt; @observable _linkDoc: Opt; @@ -74,7 +75,7 @@ export class LinkDocPreview extends React.Component { } onPointerDown = (e: PointerEvent) => { - !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview + !this._linkDocRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview }; @computed get href() { @@ -143,7 +144,7 @@ export class LinkDocPreview extends React.Component { ); }; - followLink = (e: React.PointerEvent) => { + followLink = () => { if (this._linkDoc && this._linkSrc) { LinkDocPreview.Clear(); LinkFollower.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false); @@ -151,6 +152,9 @@ export class LinkDocPreview extends React.Component { this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), 'add:right'); } }; + + followLinkPointerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.followLink); + width = () => { if (!this._targetDoc) return 225; if (this._targetDoc[WidthSym]() < this._targetDoc?.[HeightSym]()) { @@ -167,15 +171,8 @@ export class LinkDocPreview extends React.Component { }; @computed get previewHeader() { return !this._linkDoc || !this._targetDoc || !this._linkSrc ? null : ( -
-
{ - DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); - e.stopPropagation(); - LinkDocPreview.Clear(); - }}> +
+
{StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + '...' : StrCast(this._targetDoc.title)}

{StrCast(this._linkDoc.description)}

@@ -201,7 +198,27 @@ export class LinkDocPreview extends React.Component { return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : (
{!this.props.showHeader ? null : this.previewHeader} -
+
+ setupMoveUpEvents( + this, + e, + (e, down, delta) => { + if (Math.abs(e.clientX - down[0]) + Math.abs(e.clientY - down[1]) > 100) { + DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); + LinkDocPreview.Clear(); + return true; + } + return false; + }, + emptyFunction, + this.followLink, + true + ) + } + ref={this._infoRef} + style={{ maxHeight: this._toolTipText ? '100%' : undefined }}> {this._toolTipText ? ( this._toolTipText ) : ( @@ -217,7 +234,7 @@ export class LinkDocPreview extends React.Component { docViewPath={returnEmptyDoclist} ScreenToLocalTransform={Transform.Identity} isDocumentActive={returnFalse} - isContentActive={emptyFunction} + isContentActive={returnFalse} addDocument={returnFalse} removeDocument={returnFalse} addDocTab={returnFalse} @@ -232,6 +249,7 @@ export class LinkDocPreview extends React.Component { suppressSetHeight={true} PanelWidth={this.width} PanelHeight={this.height} + pointerEvents={returnNone} focus={DocUtils.DefaultFocus} whenChildContentsActiveChanged={returnFalse} ignoreAutoHeight={true} // need to ignore autoHeight otherwise autoHeight text boxes will expand beyond the preview panel size. @@ -248,7 +266,11 @@ export class LinkDocPreview extends React.Component { render() { const borders = 16; // 8px border on each side return ( -
+
{this.docPreview}
); -- cgit v1.2.3-70-g09d2 From b830a5dc8df82886a9304ebe02fe93cdf1c0b521 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jul 2022 14:10:32 -0400 Subject: fixed video thumbnail generation to have a lock that prevents multiple attempts to create the thumbnails at the same time. fixed ImageUpload to catch resize errors and not crash the server. --- .../collections/CollectionStackedTimeline.tsx | 9 +- src/client/views/nodes/VideoBox.tsx | 16 +- src/server/ApiManagers/UploadManager.ts | 556 +++++++++++---------- 3 files changed, 297 insertions(+), 284 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 714d8421a..52126884a 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -2,7 +2,7 @@ import React = require('react'); import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; @@ -289,10 +289,9 @@ export class CollectionStackedTimeline extends CollectionSubView 0 ? new ImageField(thumbnails[nearest]) : new ImageField(''); - const src = imgField && imgField.url.href ? imgField.url.href.replace('.png', '_s.png') : ''; - this._thumbnail = src ? src : undefined; + const thumbnails = StrListCast(this.dataDoc.thumbnails); + const imgField = thumbnails?.length > 0 ? new ImageField(thumbnails[nearest]) : undefined; + this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_s.png') : undefined; } } }; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 681f7c5b2..4af4d2020 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -418,31 +418,29 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - const video = document.createElement('video'); + if (this.dataDoc.thumbnails !== undefined) return; + this.dataDoc.thumbnails = new List(); const thumbnailPromises: Promise[] = []; + const video = document.createElement('video'); - video.onloadedmetadata = () => { - video.currentTime = 0; - }; + video.onloadedmetadata = () => (video.currentTime = 0); video.onseeked = () => { const canvas = document.createElement('canvas'); canvas.height = video.videoHeight; canvas.width = video.videoWidth; const ctx = canvas.getContext('2d'); - ctx?.drawImage(video, 0, 0, canvas.width, canvas.height); + ctx?.drawImage(video, 0, 0, canvas.width, canvas.height, 0, 0, 25, 25); const imgUrl = canvas.toDataURL(); const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, ''); const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_')); const filename = basename(encodedFilename); - thumbnailPromises.push(VideoBox.convertDataUri(imgUrl, filename)); + thumbnailPromises?.push(VideoBox.convertDataUri(imgUrl, filename, true)); const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1); if (newTime < video.duration) { video.currentTime = newTime; } else { - Promise.all(thumbnailPromises).then(thumbnails => { - this.dataDoc.thumbnails = new List(thumbnails); - }); + Promise.all(thumbnailPromises).then(thumbnails => (this.dataDoc.thumbnails = new List(thumbnails))); } }; diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 332ba3d35..787e331c5 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -1,291 +1,308 @@ -import ApiManager, { Registration } from "./ApiManager"; -import { Method, _success } from "../RouteManager"; +import ApiManager, { Registration } from './ApiManager'; +import { Method, _success } from '../RouteManager'; import * as formidable from 'formidable'; import v4 = require('uuid/v4'); const AdmZip = require('adm-zip'); -import { extname, basename, dirname, } from 'path'; -import { createReadStream, createWriteStream, unlink, writeFile } from "fs"; -import { publicDirectory, filesDirectory } from ".."; -import { Database } from "../database"; -import { DashUploadUtils, InjectSize, SizeSuffix } from "../DashUploadUtils"; +import { extname, basename, dirname } from 'path'; +import { createReadStream, createWriteStream, unlink, writeFile } from 'fs'; +import { publicDirectory, filesDirectory } from '..'; +import { Database } from '../database'; +import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils'; import * as sharp from 'sharp'; -import { AcceptableMedia, Upload } from "../SharedMediaTypes"; -import { normalize } from "path"; -import RouteSubscriber from "../RouteSubscriber"; +import { AcceptableMedia, Upload } from '../SharedMediaTypes'; +import { normalize } from 'path'; +import RouteSubscriber from '../RouteSubscriber'; const imageDataUri = require('image-data-uri'); -import { SolrManager } from "./SearchManager"; +import { SolrManager } from './SearchManager'; const fs = require('fs'); export enum Directory { - parsed_files = "parsed_files", - images = "images", - videos = "videos", - pdfs = "pdfs", - text = "text", - pdf_thumbnails = "pdf_thumbnails", - audio = "audio", - csv = "csv", + parsed_files = 'parsed_files', + images = 'images', + videos = 'videos', + pdfs = 'pdfs', + text = 'text', + pdf_thumbnails = 'pdf_thumbnails', + audio = 'audio', + csv = 'csv', } export function serverPathToFile(directory: Directory, filename: string) { - return normalize(`${filesDirectory}/${directory}/${filename}`); + return normalize(`${filesDirectory}/${directory}/${filename}`); } export function pathToDirectory(directory: Directory) { - return normalize(`${filesDirectory}/${directory}`); + return normalize(`${filesDirectory}/${directory}`); } export function clientPathToFile(directory: Directory, filename: string) { - return `/files/${directory}/${filename}`; + return `/files/${directory}/${filename}`; } export default class UploadManager extends ApiManager { - protected initialize(register: Registration): void { - - register({ - method: Method.POST, - subscription: "/concatVideos", - secureHandler: async ({ req, res }) => { - // req.body contains the array of server paths to the videos - _success(res, await DashUploadUtils.concatVideos(req.body)); - } - }); - - register({ - method: Method.POST, - subscription: "/uploadFormData", - secureHandler: async ({ req, res }) => { - const form = new formidable.IncomingForm(); - form.keepExtensions = true; - form.uploadDir = pathToDirectory(Directory.parsed_files); - return new Promise(resolve => { - form.parse(req, async (_err, _fields, files) => { - const results: Upload.FileResponse[] = []; - for (const key in files) { - const f = files[key]; - if (!Array.isArray(f)) { - const result = await DashUploadUtils.upload(f); - result && !(result.result instanceof Error) && results.push(result); - } - } - _success(res, results); - resolve(); - }); - }); - } - }); - - register({ - method: Method.POST, - subscription: "/uploadYoutubeVideo", - secureHandler: async ({ req, res }) => { - //req.readableBuffer.head.data - return new Promise(async resolve => { - req.addListener("data", async (args) => { - console.log(args); - const payload = String.fromCharCode.apply(String, args); - const videoId = JSON.parse(payload).videoId; - const results: Upload.FileResponse[] = []; - const result = await DashUploadUtils.uploadYoutube(videoId); - result && !(result.result instanceof Error) && results.push(result); - _success(res, results); - resolve(); - }); - }); - } - }); + register({ + method: Method.POST, + subscription: '/concatVideos', + secureHandler: async ({ req, res }) => { + // req.body contains the array of server paths to the videos + _success(res, await DashUploadUtils.concatVideos(req.body)); + }, + }); - register({ - method: Method.POST, - subscription: new RouteSubscriber("youtubeScreenshot"), - secureHandler: async ({ req, res }) => { - const { id, timecode } = req.body; - const convert = (raw: string) => { - const number = Math.floor(Number(raw)); - const seconds = number % 60; - const minutes = (number - seconds) / 60; - return `${minutes}m${seconds}s`; - }; - const suffix = timecode ? `&t=${convert(timecode)}` : ``; - const targetUrl = `https://www.youtube.com/watch?v=${id}${suffix}`; - const buffer = await captureYoutubeScreenshot(targetUrl); - if (!buffer) { - return res.send(); + register({ + method: Method.POST, + subscription: '/uploadFormData', + secureHandler: async ({ req, res }) => { + const form = new formidable.IncomingForm(); + form.keepExtensions = true; + form.uploadDir = pathToDirectory(Directory.parsed_files); + return new Promise(resolve => { + form.parse(req, async (_err, _fields, files) => { + const results: Upload.FileResponse[] = []; + for (const key in files) { + const f = files[key]; + if (!Array.isArray(f)) { + const result = await DashUploadUtils.upload(f); + result && !(result.result instanceof Error) && results.push(result); + } } - const resolvedName = `youtube_capture_${id}_${suffix}.png`; - const resolvedPath = serverPathToFile(Directory.images, resolvedName); - return new Promise(resolve => { - writeFile(resolvedPath, buffer, async error => { - if (error) { - return res.send(); - } - await DashUploadUtils.outputResizedImages(() => createReadStream(resolvedPath), resolvedName, pathToDirectory(Directory.images)); - res.send({ - accessPaths: { - agnostic: DashUploadUtils.getAccessPaths(Directory.images, resolvedName) - } - } as Upload.FileInformation); - resolve(); - }); - }); - } - }); + _success(res, results); + resolve(); + }); + }); + }, + }); - register({ - method: Method.POST, - subscription: "/uploadRemoteImage", - secureHandler: async ({ req, res }) => { + register({ + method: Method.POST, + subscription: '/uploadYoutubeVideo', + secureHandler: async ({ req, res }) => { + //req.readableBuffer.head.data + return new Promise(async resolve => { + req.addListener('data', async args => { + console.log(args); + const payload = String.fromCharCode.apply(String, args); + const videoId = JSON.parse(payload).videoId; + const results: Upload.FileResponse[] = []; + const result = await DashUploadUtils.uploadYoutube(videoId); + result && !(result.result instanceof Error) && results.push(result); + _success(res, results); + resolve(); + }); + }); + }, + }); - const { sources } = req.body; - if (Array.isArray(sources)) { - const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source))); - return res.send(results); + register({ + method: Method.POST, + subscription: new RouteSubscriber('youtubeScreenshot'), + secureHandler: async ({ req, res }) => { + const { id, timecode } = req.body; + const convert = (raw: string) => { + const number = Math.floor(Number(raw)); + const seconds = number % 60; + const minutes = (number - seconds) / 60; + return `${minutes}m${seconds}s`; + }; + const suffix = timecode ? `&t=${convert(timecode)}` : ``; + const targetUrl = `https://www.youtube.com/watch?v=${id}${suffix}`; + const buffer = await captureYoutubeScreenshot(targetUrl); + if (!buffer) { + return res.send(); + } + const resolvedName = `youtube_capture_${id}_${suffix}.png`; + const resolvedPath = serverPathToFile(Directory.images, resolvedName); + return new Promise(resolve => { + writeFile(resolvedPath, buffer, async error => { + if (error) { + return res.send(); } - res.send(); - } - }); + await DashUploadUtils.outputResizedImages(() => createReadStream(resolvedPath), resolvedName, pathToDirectory(Directory.images)); + res.send({ + accessPaths: { + agnostic: DashUploadUtils.getAccessPaths(Directory.images, resolvedName), + }, + } as Upload.FileInformation); + resolve(); + }); + }); + }, + }); - register({ - method: Method.POST, - subscription: "/uploadDoc", - secureHandler: ({ req, res }) => { + register({ + method: Method.POST, + subscription: '/uploadRemoteImage', + secureHandler: async ({ req, res }) => { + const { sources } = req.body; + if (Array.isArray(sources)) { + const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source))); + return res.send(results); + } + res.send(); + }, + }); - const form = new formidable.IncomingForm(); - form.keepExtensions = true; - // let path = req.body.path; - const ids: { [id: string]: string } = {}; - let remap = true; - const getId = (id: string): string => { - if (!remap) return id; - if (id.endsWith("Proto")) return id; - if (id in ids) { - return ids[id]; - } else { - return ids[id] = v4(); - } - }; - const mapFn = (doc: any) => { - if (doc.id) { - doc.id = getId(doc.id); - } - for (const key in doc.fields) { - if (!doc.fields.hasOwnProperty(key)) { continue; } - const field = doc.fields[key]; - if (field === undefined || field === null) { continue; } + register({ + method: Method.POST, + subscription: '/uploadDoc', + secureHandler: ({ req, res }) => { + const form = new formidable.IncomingForm(); + form.keepExtensions = true; + // let path = req.body.path; + const ids: { [id: string]: string } = {}; + let remap = true; + const getId = (id: string): string => { + if (!remap) return id; + if (id.endsWith('Proto')) return id; + if (id in ids) { + return ids[id]; + } else { + return (ids[id] = v4()); + } + }; + const mapFn = (doc: any) => { + if (doc.id) { + doc.id = getId(doc.id); + } + for (const key in doc.fields) { + if (!doc.fields.hasOwnProperty(key)) { + continue; + } + const field = doc.fields[key]; + if (field === undefined || field === null) { + continue; + } - if (field.__type === "Doc") { - mapFn(field); - } else if (field.__type === "proxy" || field.__type === "prefetch_proxy") { - field.fieldId = getId(field.fieldId); - } else if (field.__type === "script" || field.__type === "computed") { - if (field.captures) { - field.captures.fieldId = getId(field.captures.fieldId); - } - } else if (field.__type === "list") { - mapFn(field); - } else if (typeof field === "string") { - const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g; - doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => { - return `${p1}${getId(p2)}"`; - }); - } else if (field.__type === "RichTextField") { - const re = /("href"\s*:\s*")(.*?)"/g; - field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => { - return `${p1}${getId(p2)}"`; - }); - } - } - }; - return new Promise(resolve => { - form.parse(req, async (_err, fields, files) => { - remap = fields.remap !== "false"; - let id: string = ""; + if (field.__type === 'Doc') { + mapFn(field); + } else if (field.__type === 'proxy' || field.__type === 'prefetch_proxy') { + field.fieldId = getId(field.fieldId); + } else if (field.__type === 'script' || field.__type === 'computed') { + if (field.captures) { + field.captures.fieldId = getId(field.captures.fieldId); + } + } else if (field.__type === 'list') { + mapFn(field); + } else if (typeof field === 'string') { + const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g; + doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => { + return `${p1}${getId(p2)}"`; + }); + } else if (field.__type === 'RichTextField') { + const re = /("href"\s*:\s*")(.*?)"/g; + field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => { + return `${p1}${getId(p2)}"`; + }); + } + } + }; + return new Promise(resolve => { + form.parse(req, async (_err, fields, files) => { + remap = fields.remap !== 'false'; + let id: string = ''; + try { + for (const name in files) { + const f = files[name]; + const path_2 = Array.isArray(f) ? '' : f.path; + const zip = new AdmZip(path_2); + zip.getEntries().forEach((entry: any) => { + if (!entry.entryName.startsWith('files/')) return; + let directory = dirname(entry.entryName) + '/'; + const extension = extname(entry.entryName); + const base = basename(entry.entryName).split('.')[0]; try { - for (const name in files) { - const f = files[name]; - const path_2 = Array.isArray(f) ? "" : f.path; - const zip = new AdmZip(path_2); - zip.getEntries().forEach((entry: any) => { - if (!entry.entryName.startsWith("files/")) return; - let directory = dirname(entry.entryName) + "/"; - const extension = extname(entry.entryName); - const base = basename(entry.entryName).split(".")[0]; - try { - zip.extractEntryTo(entry.entryName, publicDirectory, true, false); - directory = "/" + directory; - - createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_o" + extension)); - createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_s" + extension)); - createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_m" + extension)); - createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + "_l" + extension)); - } catch (e) { - console.log(e); - } - }); - const json = zip.getEntry("doc.json"); - try { - const data = JSON.parse(json.getData().toString("utf8")); - const datadocs = data.docs; - id = getId(data.id); - const docs = Object.keys(datadocs).map(key => datadocs[key]); - docs.forEach(mapFn); - await Promise.all(docs.map((doc: any) => new Promise(res => { - Database.Instance.replace(doc.id, doc, (err, r) => { - err && console.log(err); - res(); - }, true); - }))); - } catch (e) { console.log(e); } - unlink(path_2, () => { }); - } - SolrManager.update(); - res.send(JSON.stringify(id || "error")); - } catch (e) { console.log(e); } - resolve(); - }); - }); - } - }); - - register({ - method: Method.POST, - subscription: "/inspectImage", - secureHandler: async ({ req, res }) => { + zip.extractEntryTo(entry.entryName, publicDirectory, true, false); + directory = '/' + directory; - const { source } = req.body; - if (typeof source === "string") { - return res.send(await DashUploadUtils.InspectImage(source)); + createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_o' + extension)); + createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_s' + extension)); + createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_m' + extension)); + createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_l' + extension)); + } catch (e) { + console.log(e); + } + }); + const json = zip.getEntry('doc.json'); + try { + const data = JSON.parse(json.getData().toString('utf8')); + const datadocs = data.docs; + id = getId(data.id); + const docs = Object.keys(datadocs).map(key => datadocs[key]); + docs.forEach(mapFn); + await Promise.all( + docs.map( + (doc: any) => + new Promise(res => { + Database.Instance.replace( + doc.id, + doc, + (err, r) => { + err && console.log(err); + res(); + }, + true + ); + }) + ) + ); + } catch (e) { + console.log(e); + } + unlink(path_2, () => {}); + } + SolrManager.update(); + res.send(JSON.stringify(id || 'error')); + } catch (e) { + console.log(e); } - res.send({}); - } - }); + resolve(); + }); + }); + }, + }); + + register({ + method: Method.POST, + subscription: '/inspectImage', + secureHandler: async ({ req, res }) => { + const { source } = req.body; + if (typeof source === 'string') { + return res.send(await DashUploadUtils.InspectImage(source)); + } + res.send({}); + }, + }); register({ method: Method.POST, - subscription: "/uploadURI", + subscription: '/uploadURI', secureHandler: ({ req, res }) => { const uri = req.body.uri; const filename = req.body.name; const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original; const deleteFiles = req.body.replaceRootFilename; if (!uri || !filename) { - res.status(401).send("incorrect parameters specified"); + res.status(401).send('incorrect parameters specified'); return; } if (deleteFiles) { - const path = serverPathToFile(Directory.images, ""); + const path = serverPathToFile(Directory.images, ''); const regex = new RegExp(`${deleteFiles}.*`); - fs.readdirSync(path).filter((f: any) => regex.test(f)).map((f: any) => fs.unlinkSync(path + f)); + fs.readdirSync(path) + .filter((f: any) => regex.test(f)) + .map((f: any) => fs.unlinkSync(path + f)); } return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => { const ext = extname(savedName).toLowerCase(); const { pngs, jpgs } = AcceptableMedia; - const resizers = !origSuffix ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium }] : [ - { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Small }, - { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium }, - { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Large }, - ]; + const resizers = !origSuffix + ? [{ resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium }] + : [ + { resizer: sharp().resize(100, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Small }, + { resizer: sharp().resize(400, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Medium }, + { resizer: sharp().resize(900, undefined, { withoutEnlargement: true }), suffix: SizeSuffix.Large }, + ]; let isImage = false; if (pngs.includes(ext)) { resizers.forEach(element => { @@ -301,49 +318,48 @@ export default class UploadManager extends ApiManager { if (isImage) { resizers.forEach(resizer => { const path = serverPathToFile(Directory.images, InjectSize(filename, resizer.suffix) + ext); - createReadStream(savedName).on("error", e => console.log("Resizing read:" + e)) + createReadStream(savedName) + .on('error', e => console.log('Resizing read:' + e)) .pipe(resizer.resizer) - .pipe(createWriteStream(path).on("error", e => console.log("Resizing write: " + e))); + .on('error', e => console.log('Resizing write: ' + e)) + .pipe(createWriteStream(path).on('error', e => console.log('Resizing write: ' + e))); }); - - } - res.send(clientPathToFile(Directory.images, filename + ext)); - }); - } - }); - - } - + } + res.send(clientPathToFile(Directory.images, filename + ext)); + }); + }, + }); + } } function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); } /** * On success, returns a buffer containing the bytes of a screenshot * of the video (optionally, at a timecode) specified by @param targetUrl. - * + * * On failure, returns undefined. */ async function captureYoutubeScreenshot(targetUrl: string) { - // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); - // const page = await browser.newPage(); - // // await page.setViewport({ width: 1920, height: 1080 }); + // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); + // const page = await browser.newPage(); + // // await page.setViewport({ width: 1920, height: 1080 }); - // // await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any }); + // // await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any }); - // const videoPlayer = await page.$('.html5-video-player'); - // videoPlayer && await page.focus("video"); - // await delay(7000); - // const ad = await page.$('.ytp-ad-skip-button-text'); - // await ad?.click(); - // await videoPlayer?.click(); - // await delay(1000); - // // hide youtube player controls. - // await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none'); + // const videoPlayer = await page.$('.html5-video-player'); + // videoPlayer && await page.focus("video"); + // await delay(7000); + // const ad = await page.$('.ytp-ad-skip-button-text'); + // await ad?.click(); + // await videoPlayer?.click(); + // await delay(1000); + // // hide youtube player controls. + // await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none'); - // const buffer = await videoPlayer?.screenshot({ encoding: "binary" }); - // await browser.close(); + // const buffer = await videoPlayer?.screenshot({ encoding: "binary" }); + // await browser.close(); - // return buffer; - return null; -} \ No newline at end of file + // return buffer; + return null; +} -- cgit v1.2.3-70-g09d2 From fe5b49705069eebccec22e6cad29b007a60e3bec Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jul 2022 14:22:58 -0400 Subject: from last --- src/client/views/collections/CollectionStackedTimeline.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 52126884a..48e3abbc7 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -291,7 +291,7 @@ export class CollectionStackedTimeline extends CollectionSubView 0 ? new ImageField(thumbnails[nearest]) : undefined; - this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_s.png') : undefined; + this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined; } } }; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 4af4d2020..a3d501153 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -427,15 +427,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const canvas = document.createElement('canvas'); - canvas.height = video.videoHeight; - canvas.width = video.videoWidth; - const ctx = canvas.getContext('2d'); - ctx?.drawImage(video, 0, 0, canvas.width, canvas.height, 0, 0, 25, 25); - const imgUrl = canvas.toDataURL(); + canvas.height = 100; + canvas.width = 100; + canvas.getContext('2d')?.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, 100, 100); const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, ''); const encodedFilename = encodeURIComponent('thumbnail' + retitled + '_' + video.currentTime.toString().replace(/\./, '_')); - const filename = basename(encodedFilename); - thumbnailPromises?.push(VideoBox.convertDataUri(imgUrl, filename, true)); + thumbnailPromises?.push(VideoBox.convertDataUri(canvas.toDataURL(), basename(encodedFilename), true)); const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1); if (newTime < video.duration) { video.currentTime = newTime; -- cgit v1.2.3-70-g09d2 From 131408385d49fc8d5f360c2918737da4cecc13c1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Jul 2022 16:55:01 -0400 Subject: fixed autoHeight for texdtviews embedded in text views. fixed doc decorations for sidebar items in lightbox view. added 'tree' as sidebar type for text. fixed text sidebar to show same annotations whether in stacking or tree view. fixed linkDescription pop to go away on click outside of it. --- src/client/views/EditableView.tsx | 113 ++++++++++++--------- src/client/views/nodes/LinkDescriptionPopup.tsx | 74 +++++++------- .../views/nodes/formattedText/DashDocView.tsx | 30 +++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 64 ++++++------ .../views/nodes/formattedText/RichTextMenu.tsx | 2 +- 5 files changed, 158 insertions(+), 125 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index d7707a6fe..8036df471 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -3,7 +3,7 @@ import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as Autosuggest from 'react-autosuggest'; import { ObjectField } from '../../fields/ObjectField'; -import "./EditableView.scss"; +import './EditableView.scss'; export interface EditableProps { /** @@ -26,17 +26,16 @@ export interface EditableProps { contents: any; fontStyle?: string; fontSize?: number; - height?: number | "auto"; + height?: number | 'auto'; sizeToContent?: boolean; maxHeight?: number; display?: string; overflow?: string; autosuggestProps?: { resetValue: () => void; - value: string, - onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void, - autosuggestProps: Autosuggest.AutosuggestProps - + value: string; + onChange: (e: React.ChangeEvent, { newValue }: { newValue: string }) => void; + autosuggestProps: Autosuggest.AutosuggestProps; }; oneLine?: boolean; editing?: boolean; @@ -81,16 +80,16 @@ export class EditableView extends React.Component { @action onKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { - case "Tab": + case 'Tab': e.stopPropagation(); this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false); this.props.OnTab?.(e.shiftKey); break; - case "Backspace": + case 'Backspace': e.stopPropagation(); if (!e.currentTarget.value) this.props.OnEmpty?.(); break; - case "Enter": + case 'Enter': e.stopPropagation(); if (!e.ctrlKey) { this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true); @@ -100,22 +99,26 @@ export class EditableView extends React.Component { this.props.isEditingCallback?.(false); } break; - case "Escape": + case 'Escape': e.stopPropagation(); this._editing = false; this.props.isEditingCallback?.(false); break; - case ":": + case ':': this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); break; - case "Shift": case "Alt": case "Meta": case "Control": break; + case 'Shift': + case 'Alt': + case 'Meta': + case 'Control': + break; default: if (this.props.textCallback?.(e.key)) { this._editing = false; - this.props.isEditingCallback?.(false,); + this.props.isEditingCallback?.(false); } } - } + }; @action onClick = (e: React.MouseEvent) => { @@ -129,40 +132,44 @@ export class EditableView extends React.Component { } e.stopPropagation(); } - } + }; @action finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) { if (this.props.SetValue(value, shiftDown, enterKey)) { this._editing = false; - this.props.isEditingCallback?.(false,); + this.props.isEditingCallback?.(false); } else { this._editing = false; this.props.isEditingCallback?.(false); - !lostFocus && setTimeout(action(() => { - this._editing = true; - this.props.isEditingCallback?.(true); - }), 0); + !lostFocus && + setTimeout( + action(() => { + this._editing = true; + this.props.isEditingCallback?.(true); + }), + 0 + ); } } - stopPropagation(e: React.SyntheticEvent) { e.stopPropagation(); } + stopPropagation(e: React.SyntheticEvent) { + e.stopPropagation(); + } @action setIsFocused = (value: boolean) => { const wasFocused = this._editing; this._editing = value; return wasFocused !== this._editing; - } - - + }; renderEditor() { - return this.props.autosuggestProps - ? this.finalizeEdit(e.currentTarget.value, false, true, false), @@ -171,39 +178,49 @@ export class EditableView extends React.Component { onPointerUp: this.stopPropagation, onKeyPress: this.stopPropagation, value: this.props.autosuggestProps.value, - onChange: this.props.autosuggestProps.onChange + onChange: this.props.autosuggestProps.onChange, }} /> - : this.finalizeEdit(e.currentTarget.value, false, true, false)} defaultValue={this.props.GetValue()} autoFocus={true} onKeyDown={this.onKeyDown} - onKeyPress={this.stopPropagation} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} - />; + onKeyPress={this.stopPropagation} + onPointerDown={this.stopPropagation} + onClick={this.stopPropagation} + onPointerUp={this.stopPropagation} + /> + ); } render() { if (this._editing && this.props.GetValue() !== undefined) { - return this.props.sizeToContent ? -
-
- {this.props.GetValue()} -
+ return this.props.sizeToContent ? ( +
+
{this.props.GetValue()}
{this.renderEditor()} -
: - this.renderEditor(); +
+ ) : ( + this.renderEditor() + ); } setTimeout(() => this.props.autosuggestProps?.resetValue()); - return this.props.contents instanceof ObjectField ? (null) : -
- - {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()} - -
; + return this.props.contents instanceof ObjectField ? null : ( +
e.stopPropagation()} + onClick={this.onClick} + placeholder={this.props.placeholder}> + {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()} +
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index ccac66996..91bd505c5 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -1,26 +1,24 @@ -import React = require("react"); -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../../fields/Doc"; -import { LinkManager } from "../../util/LinkManager"; -import "./LinkDescriptionPopup.scss"; -import { TaskCompletionBox } from "./TaskCompletedBox"; - +import React = require('react'); +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc } from '../../../fields/Doc'; +import { LinkManager } from '../../util/LinkManager'; +import './LinkDescriptionPopup.scss'; +import { TaskCompletionBox } from './TaskCompletedBox'; @observer export class LinkDescriptionPopup extends React.Component<{}> { - @observable public static descriptionPopup: boolean = false; - @observable public static showDescriptions: string = "ON"; + @observable public static showDescriptions: string = 'ON'; @observable public static popupX: number = 700; @observable public static popupY: number = 350; - @observable description: string = ""; + @observable description: string = ''; @observable popupRef = React.createRef(); @action descriptionChanged = (e: React.ChangeEvent) => { this.description = e.currentTarget.value; - } + }; @action onDismiss = (add: boolean) => { @@ -28,7 +26,7 @@ export class LinkDescriptionPopup extends React.Component<{}> { if (add) { LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).description = this.description); } - } + }; @action onClick = (e: PointerEvent) => { @@ -36,35 +34,43 @@ export class LinkDescriptionPopup extends React.Component<{}> { LinkDescriptionPopup.descriptionPopup = false; TaskCompletionBox.taskCompleted = false; } - } + }; @action componentDidMount() { - document.addEventListener("pointerdown", this.onClick); + document.addEventListener('pointerdown', this.onClick, true); } componentWillUnmount() { - document.removeEventListener("pointerdown", this.onClick); + document.removeEventListener('pointerdown', this.onClick, true); } render() { - return
- e.stopPropagation()} - onKeyPress={e => e.key === "Enter" && this.onDismiss(true)} - placeholder={"(Optional) Enter link description..."} - onChange={(e) => this.descriptionChanged(e)}> - -
-
this.onDismiss(false)}> Dismiss
-
this.onDismiss(true)}> Add
+ return ( +
+ e.stopPropagation()} + onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)} + placeholder={'(Optional) Enter link description...'} + onChange={e => this.descriptionChanged(e)}> +
+
this.onDismiss(false)}> + {' '} + Dismiss{' '} +
+
this.onDismiss(true)}> + {' '} + Add{' '} +
+
-
; + ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 08f255cab..73a711b9d 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -70,6 +70,8 @@ export class DashDocViewInternal extends React.Component { @observable _dashDoc: Doc | undefined; @observable _finalLayout: any; @observable _resolvedDataDoc: any; + @observable _width: number = 0; + @observable _height: number = 0; updateDoc = action((dashDoc: Doc) => { this._dashDoc = dashDoc; @@ -83,12 +85,14 @@ export class DashDocViewInternal extends React.Component { } if (this.props.width !== (this._dashDoc?._width ?? '') + 'px' || this.props.height !== (this._dashDoc?._height ?? '') + 'px') { try { + this._width = NumCast(this._dashDoc?._width); + this._height = NumCast(this._dashDoc?._height); // 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', + width: this._width + 'px', + height: this._height + 'px', }) ); } catch (e) { @@ -121,17 +125,17 @@ export class DashDocViewInternal extends React.Component { componentDidMount() { this._disposers.upater = reaction( () => this._dashDoc && NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width), - () => { + action(() => { 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', - }) - ); + this._width = NumCast(this._dashDoc._width); + this._height = NumCast(this._dashDoc._height); + const parent = this._spanRef.current?.parentNode as HTMLElement; + if (parent) { + parent.style.width = this._width + 'px'; + parent.style.height = this._height + 'px'; + } } - } + }) ); } @@ -172,8 +176,8 @@ export class DashDocViewInternal extends React.Component { ref={this._spanRef} className="dash-span" style={{ - width: this.props.width, - height: this.props.height, + width: this._width, + height: this._height, position: 'absolute', display: 'inline-block', }} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index bbb7ddc85..eb87d11a4 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -20,7 +20,7 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { ComputedField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -62,6 +62,7 @@ import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); import { text } from 'body-parser'; +import { CollectionTreeView } from '../../collections/CollectionTreeView'; const translateGoogleApi = require('translate-google-api'); export interface FormattedTextBoxProps { @@ -1500,7 +1501,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { //applyDevTools.applyDevTools(this._editorView); FormattedTextBox.Focused = this; - this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); + this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); }; @observable public static Focused: FormattedTextBox | undefined; @@ -1588,6 +1589,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + if (this.ProseRef?.children[0] !== e.nativeEvent.target) return; this.autoLink(); FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined); if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { @@ -1702,7 +1704,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.props.Document._fitContentsToBox; sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); - sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { + sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); // console.log("printting allSideBarDocs"); // console.log(this.allSidebarDocs); @@ -1716,7 +1718,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; + const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'tree' ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; return ComponentTag === CollectionStackingView ? ( ) : ( - +
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!), true)}> + +
); }; return ( diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 22ca76b2e..21326efaa 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -394,7 +394,7 @@ export class RichTextMenu extends AntimodeMenu { // remove all node type and apply the passed-in one to the selected text changeListType = (mapStyle: string) => { const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle(); - const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }); + const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle }); if (!this.view || nodeType?.attrs.mapStyle === '') return; const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list; -- cgit v1.2.3-70-g09d2 From cfa31b87f1c2a6597ed3cfb6a777126c58ace665 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 2 Aug 2022 16:28:18 -0400 Subject: Adjusted ScriptFields to have a rawScript, and updated ScrptingBoxes to create a scriptField even for scripts that don't compile. Updated CurrentUserUtils setup functions for clicks. Fixed TemplateMenu to work again. --- src/client/documents/Documents.ts | 14 +-- src/client/util/CurrentUserUtils.ts | 88 +++++++------- src/client/views/DocumentButtonBar.tsx | 15 ++- src/client/views/ScriptBox.tsx | 134 +++++++++++---------- src/client/views/TemplateMenu.tsx | 98 ++++++--------- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/nodes/DocumentView.scss | 23 ++-- src/client/views/nodes/DocumentView.tsx | 22 +++- src/client/views/nodes/FieldView.tsx | 5 +- src/client/views/nodes/ScriptingBox.tsx | 85 +++++++------ src/client/views/nodes/button/FontIconBox.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 +- src/fields/ScriptField.ts | 11 +- 13 files changed, 259 insertions(+), 252 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 51a9283f4..64d26e425 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -264,12 +264,6 @@ export class DocumentOptions { baseProto?: boolean; // is this a base prototoype dontRegisterView?: boolean; lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document. see PresBox. - 'onDoubleClick-rawScript'?: string; // onDoubleClick script in raw text form - 'onChildDoubleClick-rawScript'?: string; // onChildDoubleClick script in raw text form - 'onChildClick-rawScript'?: string; // on ChildClick script in raw text form - 'onClick-rawScript'?: string; // onClick script in raw text form - 'onCheckedClick-rawScript'?: string; // onChecked script in raw text form - 'onCheckedClick-params'?: List; // parameter list for onChecked treeview functions columnHeaders?: List; // headers for stacking views schemaHeaders?: List; // headers for schema view clipWidth?: number; // percent transition from before to after in comparisonBox @@ -1065,7 +1059,7 @@ export namespace Docs { } export function ButtonDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), 'onClick-rawScript': '-script-' }); + return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) }); } export function SliderDocument(options?: DocumentOptions) { @@ -1356,7 +1350,11 @@ export namespace DocUtils { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', documentView: Doc.name }, { _readOnly_: true }); + doc[key] = ScriptField.MakeScript( + scripts[key], + { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name }, + { _readOnly_: true } + ); } }); funcs && diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ce32595d4..f55ed6e72 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -97,6 +97,45 @@ export class CurrentUserUtils { return DocUtils.AssignScripts(DocUtils.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs); } + /// Initializes templates for editing click funcs of a document + static setupChildClickEditors(doc: Doc, field = "clickFuncs-child") { + const tempClicks = DocCast(doc[field]); + const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true}; + const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ + { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"}, + { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: "openOnRight(self.doubleClickView)"}]; + const reqdClickList = reqdTempOpts.map(opts => { + const allOpts = {...reqdClickOpts, ...opts.opts}; + const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined; + return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script,allOpts)); + }); + + const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, system: true}; + return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts)); + } + + /// Initializes templates for editing click funcs of a document + static setupClickEditorTemplates(doc: Doc, field = "template-clickFuncs") { + const tempClicks = DocCast(doc[field]); + const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, system: true}; + const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ + { opts: { title: "onClick"}, script: "console.log( 'click')"}, + { opts: { title: "onDoubleClick"}, script: "console.log( 'double click')"}, + { opts: { title: "onChildClick"}, script: "console.log( 'child click')"}, + { opts: { title: "onChildDoubleClick"}, script: "console.log( 'child double click')"}, + { opts: { title: "onCheckedClick"}, script: "console.log( heading, checked, containingTreeView)"}, + ]; + const reqdClickList = reqdTempOpts.map(opts => { + const allOpts = {...reqdClickOpts, ...opts.opts}; + const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined; + return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name}), allOpts), true, opts.opts.title); + }); + + const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, system: true}; + return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts)); + } + + /// Initializes templates that can be applied to notes static setupNoteTemplates(doc: Doc, field="template-notes") { const tempNotes = DocCast(doc[field]); @@ -105,7 +144,7 @@ export class CurrentUserUtils { { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" }, { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; const reqdNoteList = reqdTempOpts.map(opts => { - const reqdOpts = {...opts, title: "text", system: true}; + const reqdOpts = {...opts, title: "text", width:200, system: true}; const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined; return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note"); }); @@ -118,10 +157,10 @@ export class CurrentUserUtils { static setupDocTemplates(doc: Doc, field="myTemplates") { DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"}); const templates = [ - DocCast(doc.presElement), CurrentUserUtils.setupNoteTemplates(doc), CurrentUserUtils.setupClickEditorTemplates(doc) ]; + CurrentUserUtils.setupChildClickEditors(doc) const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; return DocUtils.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts); @@ -748,51 +787,6 @@ export class CurrentUserUtils { DocUtils.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" }); return myImports; } - - static setupClickEditorTemplates(doc: Doc) { - if (doc["clickFuncs-child"] === undefined) { - // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target - const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ", - { thisContainer: Doc.name }), { - title: "Click to open in target", _width: 300, _height: 200, - targetScriptKey: "onChildClick", system: true - }); - - const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "openOnRight(self.doubleClickView)", {}), - { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); - - doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true }); - } - - if (doc.clickFuncs === undefined) { - const onClick = Docs.Create.ScriptingDocument(undefined, { - title: "onClick", "onClick-rawScript": "console.log('click')", - isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200, system: true - }, "onClick"); - const onChildClick = Docs.Create.ScriptingDocument(undefined, { - title: "onChildClick", "onChildClick-rawScript": "console.log('child click')", - isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200, system: true - }, "onChildClick"); - const onDoubleClick = Docs.Create.ScriptingDocument(undefined, { - title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')", - isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200, system: true - }, "onDoubleClick"); - const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, { - title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')", - isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200, system: true - }, "onChildDoubleClick"); - const onCheckedClick = Docs.Create.ScriptingDocument(undefined, { - title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", - "onCheckedClick-params": new List(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, - isTemplateForField: "onCheckedClick", _width: 300, _height: 200, system: true - }, "onCheckedClick"); - doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true }); - } - - return doc.clickFuncs as Doc; - } - /// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be /// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field /// whether to revert to "default" values, or to leave them as the user/system last set them. diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index bac51a11d..1d4056759 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -335,8 +335,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); } @observable _aliasDown = false; - onAliasButtonDown = action((e: React.PointerEvent): void => { - this.props.views()[0]?.select(false); + onTemplateButton = action((e: React.PointerEvent): void => { this._tooltipOpen = false; setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction); }); @@ -374,7 +373,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
) }> -
+
{}
@@ -412,15 +411,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
) : null} - {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
- {this.templateButton} -
- /*
+ { + Doc.noviceMode ? null :
{this.templateButton}
+ /*
{this.metadataButton}
{this.contextButton} -
*/} +
*/ + } {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null :
{this.shareButton}
} diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index b7ea124b9..416162aeb 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -1,17 +1,16 @@ -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc, Opt } from "../../fields/Doc"; -import { ScriptField } from "../../fields/ScriptField"; -import { ScriptCast } from "../../fields/Types"; -import { emptyFunction } from "../../Utils"; -import { DragManager } from "../util/DragManager"; -import { CompileScript } from "../util/Scripting"; -import { EditableView } from "./EditableView"; -import { DocumentIconContainer } from "./nodes/DocumentIcon"; -import { OverlayView } from "./OverlayView"; -import "./ScriptBox.scss"; - +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, Opt } from '../../fields/Doc'; +import { ScriptField } from '../../fields/ScriptField'; +import { ScriptCast } from '../../fields/Types'; +import { emptyFunction } from '../../Utils'; +import { DragManager } from '../util/DragManager'; +import { CompileScript } from '../util/Scripting'; +import { EditableView } from './EditableView'; +import { DocumentIconContainer } from './nodes/DocumentIcon'; +import { OverlayView } from './OverlayView'; +import './ScriptBox.scss'; export interface ScriptBoxProps { onSave: (text: string, onError: (error: string) => void) => void; @@ -28,53 +27,58 @@ export class ScriptBox extends React.Component { constructor(props: ScriptBoxProps) { super(props); - this._scriptText = props.initialText || ""; + this._scriptText = props.initialText || ''; } @action onChange = (e: React.ChangeEvent) => { this._scriptText = e.target.value; - } + }; @action onError = (error: string) => { - console.log("ScriptBox: " + error); - } + console.log('ScriptBox: ' + error); + }; overlayDisposer?: () => void; onFocus = () => { this.overlayDisposer?.(); this.overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); - } + }; onBlur = () => { this.overlayDisposer?.(); - } + }; render() { - let onFocus: Opt<() => void> = undefined, onBlur: Opt<() => void> = undefined; + let onFocus: Opt<() => void> = undefined, + onBlur: Opt<() => void> = undefined; if (this.props.showDocumentIcons) { onFocus = this.onFocus; onBlur = this.onBlur; } - const params = ""} - SetValue={(value: string) => this.props.setParams && this.props.setParams(value.split(" ").filter(s => s !== " ")) ? true : true} - />; + const params = ''} SetValue={(value: string) => (this.props.setParams?.(value.split(' ').filter(s => s !== ' ')) ? true : true)} />; return (
-
+
-
{params}
+
{params}
- - + +
); @@ -90,35 +94,43 @@ export class ScriptBox extends React.Component { // tslint:disable-next-line: no-unnecessary-callback-wrapper const params: string[] = []; const setParams = (p: string[]) => params.splice(0, params.length, ...p); - const scriptingBox = { - if (!text) { - Doc.GetProto(doc)[fieldKey] = undefined; - } else { - const script = CompileScript(text, { - params: { this: Doc.name, ...contextParams }, - typecheck: false, - editable: true, - transformer: DocumentIconContainer.getTransformer() - }); - if (!script.compiled) { - onError(script.errors.map(error => error.messageText).join("\n")); - return; - } + const scriptingBox = ( + { + if (!text) { + Doc.GetProto(doc)[fieldKey] = undefined; + } else { + const script = CompileScript(text, { + params: { this: Doc.name, ...contextParams }, + typecheck: false, + editable: true, + transformer: DocumentIconContainer.getTransformer(), + }); + if (!script.compiled) { + onError(script.errors.map(error => error.messageText).join('\n')); + return; + } - const div = document.createElement("div"); - div.style.width = "90"; - div.style.height = "20"; - div.style.background = "gray"; - div.style.position = "absolute"; - div.style.display = "inline-block"; - div.style.transform = `translate(${clientX}px, ${clientY}px)`; - div.innerHTML = "button"; - params.length && DragManager.StartButtonDrag([div], text, doc.title + "-instance", {}, params, (button: Doc) => { }, clientX, clientY); + const div = document.createElement('div'); + div.style.width = '90'; + div.style.height = '20'; + div.style.background = 'gray'; + div.style.position = 'absolute'; + div.style.display = 'inline-block'; + div.style.transform = `translate(${clientX}px, ${clientY}px)`; + div.innerHTML = 'button'; + params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, (button: Doc) => {}, clientX, clientY); - Doc.GetProto(doc)[fieldKey] = new ScriptField(script); - overlayDisposer(); - } - }} showDocumentIcons />; + Doc.GetProto(doc)[fieldKey] = new ScriptField(script); + overlayDisposer(); + } + }} + showDocumentIcons + /> + ); overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title }); } } diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 156513f47..863829a51 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -55,16 +55,12 @@ export class TemplateMenu extends React.Component { @observable private _hidden: boolean = true; toggleLayout = (e: React.ChangeEvent, layout: string): void => { - this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout)); + this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout, undefined, true)); }; toggleDefault = (e: React.ChangeEvent): void => { this.props.docViews.map(dv => dv.switchViews(false, 'layout')); }; - toggleAudio = (e: React.ChangeEvent): void => { - this.props.docViews.map(dv => (dv.props.Document._showAudio = e.target.checked)); - }; - @undoBatch @action toggleTemplate = (event: React.ChangeEvent, template: string): void => { @@ -76,12 +72,6 @@ export class TemplateMenu extends React.Component { this._hidden = !this._hidden; }; - @undoBatch - @action - toggleChrome = (): void => { - this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout => (layout._chromeHidden = !layout._chromeHidden)); - }; - // todo: add brushes to brushMap to save with a style name onCustomKeypress = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { @@ -95,13 +85,9 @@ export class TemplateMenu extends React.Component { .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); } - return100 = () => 100; + return100 = () => 300; @computed get scriptField() { - const script = ScriptField.MakeScript( - 'docs.map(d => switchView(d, this))', - { this: Doc.name, heading: 'string', checked: 'string', containingTreeView: Doc.name, firstDoc: Doc.name }, - { docs: new List(this.props.docViews.map(dv => dv.props.Document)) } - ); + const script = ScriptField.MakeScript('docs.map(d => switchView(d, this))', { this: Doc.name }, { docs: this.props.docViews.map(dv => dv.props.Document) as any }); return script ? () => script : undefined; } templateIsUsed = (selDoc: Doc, templateDoc: Doc) => { @@ -113,13 +99,10 @@ export class TemplateMenu extends React.Component { const firstDoc = this.props.docViews[0].props.Document; const templateName = StrCast(firstDoc.layoutKey, 'layout').replace('layout_', ''); const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data); - const addedTypes = Doc.noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()['template-buttons'], Doc, null)?.data); - const layout = Doc.Layout(firstDoc); + const addedTypes = DocListCast(Cast(Doc.UserDoc()['template-clickFuncs'], Doc, null)?.data); const templateMenu: Array = []; this.props.templates?.forEach((checked, template) => templateMenu.push()); - templateMenu.push(); templateMenu.push(); - !Doc.noviceMode && templateMenu.push(); addedTypes.concat(noteTypes).map(template => (template.treeViewChecked = this.templateIsUsed(firstDoc, template))); this._addedKeys && Array.from(this._addedKeys) @@ -129,43 +112,42 @@ export class TemplateMenu extends React.Component {
    {Doc.noviceMode ? null : } {templateMenu} - {Doc.noviceMode ? null : ( - - )} +
); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 6850fb23a..a6c367ff7 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -296,7 +296,7 @@ export class CollectionStackingView extends CollectionSubView.documentView-node { + > .documentView-node { position: absolute; } } @@ -158,7 +158,7 @@ top: 0; width: 100%; height: 14; - background: rgba(0, 0, 0, .4); + background: rgba(0, 0, 0, 0.4); text-align: center; text-overflow: ellipsis; white-space: pre; @@ -187,19 +187,18 @@ transition: opacity 0.5s; } } - } .documentView-node:hover, .documentView-node-topmost:hover { - >.documentView-styleWrapper { - >.documentView-titleWrapper-hover { + > .documentView-styleWrapper { + > .documentView-titleWrapper-hover { display: inline-block; } } - >.documentView-styleWrapper { - >.documentView-captionWrapper { + > .documentView-styleWrapper { + > .documentView-captionWrapper { opacity: 1; } } @@ -225,6 +224,6 @@ .documentView-node:first-child { position: relative; - background: "#B59B66"; //$white; + background: '#B59B66'; //$white; } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8847c0c6a..edaa40bad 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -177,6 +177,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps { dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling NativeWidth?: () => number; NativeHeight?: () => number; + NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps LayoutTemplate?: () => Opt; contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[]; onClick?: () => ScriptField; @@ -191,7 +192,6 @@ export interface DocumentViewProps extends DocumentViewSharedProps { export interface DocumentViewInternalProps extends DocumentViewProps { NativeWidth: () => number; NativeHeight: () => number; - NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps isSelected: (outsideReaction?: boolean) => boolean; select: (ctrlPressed: boolean) => void; DocumentView: () => DocumentView; @@ -716,7 +716,7 @@ export class DocumentViewInternal extends DocComponent this.props.removeDocument?.(this.props.Document); - @undoBatch setToggleDetail = () => (this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace('layout_', '')}")`, { documentView: 'any' })); + @undoBatch setToggleDetail = () => + (this.Document.onClick = ScriptField.MakeScript( + `toggleDetail(documentView, "${StrCast(this.Document.layoutKey) + .replace('layout_', '') + .replace(/^layout$/, 'detail')}")`, + { documentView: 'any' } + )); @undoBatch @action @@ -1538,11 +1544,15 @@ export class DocumentView extends React.Component { Doc.setNativeView(this.props.Document); custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined); }; - switchViews = action((custom: boolean, view: string, finished?: () => void) => { + switchViews = action((custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => { this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc setTimeout( action(() => { - this.setCustomView(custom, view); + if (useExistingLayout && custom && this.rootDoc['layout_' + view]) { + this.rootDoc.layoutKey = 'layout_' + view; + } else { + this.setCustomView(custom, view); + } this.docView && (this.docView._animateScalingTo = 1); // expand it setTimeout( action(() => { @@ -1646,7 +1656,7 @@ ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout'); - else dv.switchViews(true, detailLayoutKeySuffix); + else dv.switchViews(true, detailLayoutKeySuffix, undefined, true); }); ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 5a6c49809..dd2c13391 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -4,10 +4,9 @@ import { observer } from 'mobx-react'; import { DateField } from '../../../fields/DateField'; import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; -import { WebField } from '../../../fields/URLField'; -import { DocumentView, DocumentViewSharedProps } from './DocumentView'; import { ScriptField } from '../../../fields/ScriptField'; -import { RecordingBox } from './RecordingBox'; +import { WebField } from '../../../fields/URLField'; +import { DocumentViewSharedProps } from './DocumentView'; // // these properties get assigned through the render() method of the DocumentView when it creates this node. diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 05ff40f22..1c9b0bc0e 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -6,7 +6,7 @@ import { Doc } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { returnEmptyString } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; @@ -60,6 +60,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent !p.startsWith('_')) + .map(key => key + ':' + params[key]); + } + } } // vars included in fields that store parameters types and names and the script itself @@ -70,30 +78,30 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent p.split(':')[1].trim()); } @computed({ keepAlive: true }) get rawScript() { - return StrCast(this.dataDoc[this.props.fieldKey + '-rawScript'], ''); + return ScriptCast(this.rootDoc[this.fieldKey])?.script.originalScript ?? ''; } @computed({ keepAlive: true }) get functionName() { - return StrCast(this.dataDoc[this.props.fieldKey + '-functionName'], ''); + return StrCast(this.rootDoc[this.props.fieldKey + '-functionName'], ''); } @computed({ keepAlive: true }) get functionDescription() { - return StrCast(this.dataDoc[this.props.fieldKey + '-functionDescription'], ''); + return StrCast(this.rootDoc[this.props.fieldKey + '-functionDescription'], ''); } @computed({ keepAlive: true }) get compileParams() { - return Cast(this.dataDoc[this.props.fieldKey + '-params'], listSpec('string'), []); + return Cast(this.rootDoc[this.props.fieldKey + '-params'], listSpec('string'), []); } set rawScript(value) { - this.dataDoc[this.props.fieldKey + '-rawScript'] = value; + Doc.SetInPlace(this.rootDoc, this.props.fieldKey, new ScriptField(undefined, undefined, value), true); } set functionName(value) { - this.dataDoc[this.props.fieldKey + '-functionName'] = value; + Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-functionName', value, true); } set functionDescription(value) { - this.dataDoc[this.props.fieldKey + '-functionDescription'] = value; + Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-functionDescription', value, true); } set compileParams(value) { - this.dataDoc[this.props.fieldKey + '-params'] = new List(value); + Doc.SetInPlace(this.rootDoc, this.props.fieldKey + '-params', new List(value), true); } getValue(result: any, descrip: boolean) { @@ -107,8 +115,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent { const area = document.querySelector('textarea'); @@ -171,13 +178,13 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent (params[p.split(':')[0].trim()] = p.split(':')[1].trim())); - const result = CompileScript(this.rawScript, { + const result = CompileScript(this.rawText, { editable: true, transformer: DocumentIconContainer.getTransformer(), params, typecheck: false, }); - this.dataDoc[this.fieldKey] = result.compiled ? new ScriptField(result) : undefined; + Doc.SetInPlace(this.rootDoc, this.fieldKey, result.compiled ? new ScriptField(result, undefined, this.rawText) : new ScriptField(undefined, undefined, this.rawText), true); this.onError(result.compiled ? undefined : result.errors); return result.compiled; }; @@ -187,9 +194,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent { if (this.onCompile()) { const bindings: { [name: string]: any } = {}; - this.paramsNames.forEach(key => (bindings[key] = this.dataDoc[key])); + this.paramsNames.forEach(key => (bindings[key] = this.rootDoc[key])); // binds vars so user doesnt have to refer to everything as self. - ScriptCast(this.dataDoc[this.fieldKey], null)?.script.run({ self: this.rootDoc, this: this.layoutDoc, ...bindings }, this.onError); + ScriptCast(this.rootDoc[this.fieldKey], null)?.script.run({ self: this.rootDoc, this: this.layoutDoc, ...bindings }, this.onError); } }; @@ -257,14 +264,14 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent { - this.dataDoc[fieldKey] = de.complete.docDragData?.droppedDocuments[0]; + Doc.SetInPlace(this.rootDoc, fieldKey, de.complete.docDragData?.droppedDocuments[0], true); e.stopPropagation(); }; // deletes a param from all areas in which it is stored @action onDelete = (num: number) => { - this.dataDoc[this.paramsNames[num]] = undefined; + Doc.SetInPlace(this.rootDoc, this.paramsNames[num], undefined, true); this.compileParams.splice(num, 1); return true; }; @@ -274,7 +281,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent { //@ts-ignore const val = e.target.selectedOptions[0].value; - this.dataDoc[name] = val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true'; + Doc.SetInPlace(this.rootDoc, name, val[0] === 'S' ? val.substring(1) : val[0] === 'N' ? parseInt(val.substring(1)) : val.substring(1) === 'true', true); }; // creates a copy of the script document @@ -330,8 +337,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent this.dataDoc[parameter]?.title ?? 'undefined'} + contents={StrCast(DocCast(this.rootDoc[parameter])?.title, 'undefined')} + GetValue={() => StrCast(DocCast(this.rootDoc[parameter])?.title, 'undefined')} SetValue={action((value: string) => { const script = CompileScript(value, { addReturn: true, @@ -341,7 +348,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent e.stopPropagation()} onChange={e => this.viewChanged(e, parameter)} - value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}> + value={typeof this.rootDoc[parameter] === 'string' ? 'S' + StrCast(this.rootDoc[parameter]) : typeof this.rootDoc[parameter] === 'number' ? 'N' + NumCast(this.rootDoc[parameter]) : 'B' + BoolCast(this.rootDoc[parameter])}> {types.map(type => (
); -- cgit v1.2.3-70-g09d2 From bca0026764fe554e1066b2f432e749371450d239 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 9 Aug 2022 11:35:00 -0400 Subject: added an audio annotation button to the anchor menu bar. --- src/client/views/DocumentButtonBar.tsx | 12 +- src/client/views/MarqueeAnnotator.tsx | 1 + src/client/views/SidebarAnnos.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 35 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 11 ++ src/client/views/pdf/AnchorMenu.tsx | 14 ++ src/fields/documentSchemas.ts | 182 ++++++++++----------- 7 files changed, 145 insertions(+), 111 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 265df3abc..81e417fca 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -21,7 +21,7 @@ import './DocumentButtonBar.scss'; import { Colors } from './global/globalEnums'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { TemplateMenu } from './TemplateMenu'; @@ -347,7 +347,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV onClick={undoBatch( action(e => { this._isRecording = true; - this.props.views().map(view => view?.docView?.recordAudioAnnotation(action(() => (this._isRecording = false)))); + this.props.views().map( + view => + view && + DocumentViewInternal.recordAudioAnnotation( + view.dataDoc, + view.LayoutFieldKey, + action(() => (this._isRecording = false)) + ) + ); }) )}> diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index b01ee5f42..f90ad8bb5 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -65,6 +65,7 @@ export class MarqueeAnnotator extends React.Component { AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('rgba(173, 216, 230, 0.75)', true), true); AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight('rgba(173, 216, 230, 0.75)', true)); + AnchorMenu.Instance.OnAudio = unimplementedFunction; AnchorMenu.Instance.Highlight = this.highlight; AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations); AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor; diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 1c14c7cd5..90d9c3c43 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -74,6 +74,7 @@ export class SidebarAnnos extends React.Component { DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on'); this.addDocument(target); this._stackRef.current?.focusDocument(target); + return target; }; makeDocUnfiltered = (doc: Doc) => { if (DocListCast(this.props.rootDoc[this.sidebarKey]).includes(doc)) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4b2bd07ef..f87581875 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -206,7 +206,6 @@ export class DocumentViewInternal extends DocComponent (this.layoutDoc._showAudio = !this.layoutDoc._showAudio)), icon: 'microphone' }); const existingOnClick = cm.findByDescription('OnClick...'); const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; @@ -1010,10 +1008,15 @@ export class DocumentViewInternal extends DocComponent{audioTextAnnos?.lastElement()}
}>
- +
); @@ -1154,7 +1157,7 @@ export class DocumentViewInternal extends DocComponent { - console.log('PLAYED'); - self._mediaState = 0; + self.dataDoc.audioAnnoState = 0; }); }, }); - this._mediaState = 1; + this.dataDoc.audioAnnoState = 1; } }; - recordAudioAnnotation = (onEnd?: () => void) => { + static recordAudioAnnotation(dataDoc: Doc, field: string, onEnd?: () => void) { let gumStream: any; let recorder: any; - const self = this; navigator.mediaDevices .getUserMedia({ audio: true, }) .then(function (stream) { - let audioTextAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null); + let audioTextAnnos = Cast(dataDoc[field + '-audioAnnotations-text'], listSpec('string'), null); if (audioTextAnnos) audioTextAnnos.push(''); - else audioTextAnnos = self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'] = new List(['']); + else audioTextAnnos = dataDoc[field + '-audioAnnotations-text'] = new List(['']); DictationManager.Controls.listen({ interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), continuous: { indefinite: false }, @@ -1200,24 +1201,24 @@ export class DocumentViewInternal extends DocComponent (self._mediaState = 2)); + runInAction(() => (dataDoc.audioAnnoState = 2)); recorder.start(); setTimeout(() => { recorder.stop(); DictationManager.Controls.stop(false); - runInAction(() => (self._mediaState = 0)); + runInAction(() => (dataDoc.audioAnnoState = 0)); gumStream.getAudioTracks()[0].stop(); }, 5000); }); - }; + } captionStyleProvider = (doc: Opt, props: Opt, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption'); @computed get innards() { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 929cca1ea..223441b3b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -63,6 +63,7 @@ import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); import { text } from 'body-parser'; import { CollectionTreeView } from '../../collections/CollectionTreeView'; +import { DocumentViewInternal } from '../DocumentView'; const translateGoogleApi = require('translate-google-api'); export interface FormattedTextBoxProps { @@ -249,6 +250,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + !this.layoutDoc.showSidebar && this.toggleSidebar(); + const anchor = this.getAnchor(); + const target = this._sidebarRef.current?.anchorMenuClick(anchor); + if (target) { + anchor.followLinkAudio = true; + DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target)); + target.title = ComputedField.MakeFunction(`self["text-audioAnnotations-text"].lastElement()`); + } + }; AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => { this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch); return undefined; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 1a1120b6c..ee2ae10a7 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -49,6 +49,7 @@ export class AnchorMenu extends AntimodeMenu { public OnCrop: (e: PointerEvent) => void = unimplementedFunction; public OnClick: (e: PointerEvent) => void = unimplementedFunction; + public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public Highlight: (color: string, isPushpin: boolean) => Opt = (color: string, isPushpin: boolean) => undefined; @@ -92,6 +93,10 @@ export class AnchorMenu extends AntimodeMenu { ); }; + audioDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e)); + }; + cropDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -196,6 +201,15 @@ export class AnchorMenu extends AntimodeMenu { , + AnchorMenu.Instance.OnAudio === unimplementedFunction ? ( + <> + ) : ( + {'Click to Record Annotation'}
}> + + + ), //NOTE: link popup is currently in progress {'Find document to link to selected text'}
}>
}> + {`Show Pivot Viewer for '${this._fieldKey}'`}
}> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 223441b3b..e027ab0bd 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1884,7 +1884,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent -- cgit v1.2.3-70-g09d2 From a63f017c213563728f45f2caa7415843f50f3559 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 9 Aug 2022 19:50:33 -0400 Subject: fixed click on menu button for text box with hyperlink to not bring up buttonBar. fixed right click on hyperlink to just bring up button bar, but not native context menu. fixe stacking view child pointer events so that sidebar documents in lightbox are editable. --- src/client/views/DocumentButtonBar.tsx | 7 +------ src/client/views/collections/CollectionStackingView.tsx | 5 +++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 10 +++++----- 3 files changed, 9 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 81e417fca..1f8550ad6 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -274,12 +274,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get menuButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? null : ( - -
{`Open Context Menu`}
- - }> + {`Open Context Menu`}
}>
this.openContextMenu(e)}>
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ef68cadd7..d4efef47a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -300,9 +300,10 @@ export class CollectionStackingView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); + isContentActive = () => (this.props.isSelected() || this.props.isContentActive() ? true : this.props.isSelected() === false || this.props.isContentActive() === false ? false : undefined); - isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)); + isChildContentActive = () => + this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list getDisplayDoc(doc: Doc, width: () => number) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e027ab0bd..f61533619 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -703,9 +703,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent node that wraps the hyerlink + let target = e.target as any; // hrefs are stored on the database of the
node that wraps the hyerlink while (target && !target.dataset?.targethrefs) target = target.parentElement; - if (target) { + if (target && !(e.nativeEvent as any).dash) { const hrefs = (target.dataset?.targethrefs as string) ?.trim() .split(' ') @@ -724,6 +724,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu(); + if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; - if (this.props.isContentActive(true)) { + if (this.props.isContentActive(true) && !(e.nativeEvent as any).dash) { const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); @@ -1795,7 +1796,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent ) : (
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}> - //@ts-ignore Date: Wed, 10 Aug 2022 14:52:04 -0400 Subject: cleaned up pinning documents with an activeFrame index (and special case of isInkMask). removed text-color from pres boxes which was breaking opacity when docs had an appearFrame > 0. --- src/client/views/DocumentButtonBar.tsx | 18 +- src/client/views/collections/CollectionMenu.tsx | 25 ++- src/client/views/collections/TabDocView.tsx | 15 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 208 ++++++++++++--------- src/client/views/nodes/DocumentView.tsx | 4 +- src/client/views/nodes/trails/PresBox.tsx | 23 +-- 6 files changed, 163 insertions(+), 130 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 1f8550ad6..40fc8dae6 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,7 +5,7 @@ import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, NumCast } from '../../fields/Types'; +import { Cast, DocCast, NumCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; @@ -238,15 +238,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
- TabDocView.PinDoc( - this.props - .views() - .filter(v => v) - .map(dv => dv!.rootDoc), - { pinDocView: true } - ) - }> + onClick={e => { + const docs = this.props + .views() + .filter(v => v) + .map(dv => dv!.rootDoc); + TabDocView.PinDoc(docs, { pinDocView: true, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + }}>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index cfbcec2d6..8432619de 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -769,6 +769,21 @@ export class CollectionFreeFormViewChrome extends React.Component { @@ -1569,13 +1584,5 @@ export class CollectionGridViewChrome extends React.Component { pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } //save position - if (pinProps?.setPosition || pinDoc.isInkMask) { - pinDoc.setPosition = true; - pinDoc.y = doc.y; - pinDoc.x = doc.x; - pinDoc.presHideAfter = true; - pinDoc.presHideBefore = true; + if (pinProps?.activeFrame !== undefined) { + pinDoc.presActiveFrame = pinProps?.activeFrame; pinDoc.title = doc.title + ' (move)'; - pinDoc.presMovement = PresMovement.None; + pinDoc.presMovement = PresMovement.Pan; + if (pinDoc.isInkMask) { + pinDoc.presHideAfter = true; + pinDoc.presHideBefore = true; + pinDoc.presMovement = PresMovement.None; + } } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; PresBox.Instance?._selectedArray.clear(); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 284584a3d..e154e8445 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,26 +1,26 @@ -import { action, computed, observable, trace } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, Opt } from "../../../fields/Doc"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { TraceMobx } from "../../../fields/util"; -import { DashColor, numberRange, OmitKeys } from "../../../Utils"; -import { DocumentManager } from "../../util/DocumentManager"; -import { SelectionManager } from "../../util/SelectionManager"; -import { Transform } from "../../util/Transform"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { DocComponent } from "../DocComponent"; -import { InkingStroke } from "../InkingStroke"; -import { StyleProp } from "../StyleProvider"; -import "./CollectionFreeFormDocumentView.scss"; -import { DocumentView, DocumentViewProps } from "./DocumentView"; -import React = require("react"); +import { action, computed, observable, trace } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, Opt } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { ComputedField } from '../../../fields/ScriptField'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; +import { DashColor, numberRange, OmitKeys } from '../../../Utils'; +import { DocumentManager } from '../../util/DocumentManager'; +import { SelectionManager } from '../../util/SelectionManager'; +import { Transform } from '../../util/Transform'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { DocComponent } from '../DocComponent'; +import { InkingStroke } from '../InkingStroke'; +import { StyleProp } from '../StyleProvider'; +import './CollectionFreeFormDocumentView.scss'; +import { DocumentView, DocumentViewProps } from './DocumentView'; +import React = require('react'); export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; - sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined; + dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; + sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined; renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; highlight?: boolean; @@ -32,29 +32,51 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @observer export class CollectionFreeFormDocumentView extends DocComponent() { - public static animFields = ["_height", "_width", "x", "y", "_scrollTop", "opacity"]; // fields that are configured to be animatable using animation frames + public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; - get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive - get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; } - get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`; } - get X() { return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x); } - get Y() { return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y); } - get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); } - get Opacity() { return this.dataProvider ? this.dataProvider.opacity : undefined; } - get Highlight() { return this.dataProvider?.highlight; } - @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt); } - @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); } - @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); } + get displayName() { + return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')'; + } // this makes mobx trace() statements more descriptive + get maskCentering() { + return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; + } + get transform() { + return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`; + } + get X() { + return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x); + } + get Y() { + return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y); + } + get ZInd() { + return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); + } + get Opacity() { + return this.dataProvider ? this.dataProvider.opacity : undefined; + } + get Highlight() { + return this.dataProvider?.highlight; + } + @computed get ShowTitle() { + return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt; + } + @computed get dataProvider() { + return this.props.dataProvider?.(this.props.Document, this.props.replica); + } + @computed get sizeProvider() { + return this.props.sizeProvider?.(this.props.Document, this.props.replica); + } styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children return this.props.styleProvider?.(doc, props, property); - } + }; public static getValues(doc: Doc, time: number) { return CollectionFreeFormDocumentView.animFields.reduce((p, val) => { - p[val] = Cast(`${val}-indexed`, listSpec("number"), [NumCast(doc[val])]).reduce((p, v, i) => (i <= Math.round(time) && v !== undefined) || p === undefined ? v : p, undefined as any as number); + p[val] = Cast(`${val}-indexed`, listSpec('number'), [NumCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number); return p; }, {} as { [val: string]: Opt }); } @@ -62,35 +84,43 @@ export class CollectionFreeFormDocumentView extends DocComponent }) { const timecode = Math.round(time); Object.keys(vals).forEach(val => { - const findexed = Cast(d[`${val}-indexed`], listSpec("number"), []).slice(); + const findexed = Cast(d[`${val}-indexed`], listSpec('number'), []).slice(); findexed[timecode] = vals[val] as any as number; d[`${val}-indexed`] = new List(findexed); }); - d.appearFrame && (d["text-color"] = - d.appearFrame === timecode + 1 ? "red" : - d.appearFrame < timecode + 1 ? "grey" : "black"); } public static updateKeyframe(docs: Doc[], time: number, targetDoc?: Doc) { const timecode = Math.round(time); - docs.forEach(action(doc => { - doc._viewTransition = doc.dataTransition = "all 1s"; - doc["text-color"] = - !doc.appearFrame || !targetDoc ? "black" : - doc.appearFrame === timecode + 1 ? StrCast(targetDoc["pres-text-color"]) : - doc.appearFrame < timecode + 1 ? StrCast(targetDoc["pres-text-viewed-color"]) : - "black"; - CollectionFreeFormDocumentView.animFields.forEach(val => { - const findexed = Cast(doc[`${val}-indexed`], listSpec("number"), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); - }); - })); - setTimeout(() => docs.forEach(doc => { doc._viewTransition = undefined; doc.dataTransition = "inherit"; }), 1010); + docs.forEach( + action(doc => { + doc._viewTransition = doc.dataTransition = 'all 1s'; + CollectionFreeFormDocumentView.animFields.forEach(val => { + const findexed = Cast(doc[`${val}-indexed`], listSpec('number'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); + }); + }) + ); + setTimeout( + () => + docs.forEach(doc => { + doc._viewTransition = undefined; + doc.dataTransition = 'inherit'; + }), + 1010 + ); } public static gotoKeyframe(docs: Doc[]) { - docs.forEach(doc => doc._viewTransition = doc.dataTransition = "all 1s"); - setTimeout(() => docs.forEach(doc => { doc._viewTransition = undefined; doc.dataTransition = "inherit"; }), 1010); + docs.forEach(doc => (doc._viewTransition = doc.dataTransition = 'all 1s')); + setTimeout( + () => + docs.forEach(doc => { + doc._viewTransition = undefined; + doc.dataTransition = 'inherit'; + }), + 1010 + ); } public static setupZoom(doc: Doc, targDoc: Doc) { @@ -102,21 +132,22 @@ export class CollectionFreeFormDocumentView extends DocComponent { if (doc.appearFrame === undefined) doc.appearFrame = currTimecode; - if (!doc["opacity-indexed"]) { // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in - doc["opacity-indexed"] = new List(numberRange(currTimecode + 1).map(t => !doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)); + if (!doc['opacity-indexed']) { + // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in + doc['opacity-indexed'] = new List(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1))); } - CollectionFreeFormDocumentView.animFields.forEach(val => doc[val] = ComputedField.MakeInterpolated(val, "activeFrame", doc, currTimecode)); - doc.activeFrame = ComputedField.MakeFunction("self.context?._currentFrame||0"); - doc.dataTransition = "inherit"; + CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolated(val, 'activeFrame', doc, currTimecode))); + doc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0'); + doc.dataTransition = 'inherit'; }); } @@ -131,7 +162,7 @@ export class CollectionFreeFormDocumentView extends DocComponent SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0); } - } + }; nudge = (x: number, y: number) => { this.props.Document.x = NumCast(this.props.Document.x) + x; this.props.Document.y = NumCast(this.props.Document.y) + y; - } - panelWidth = () => (this.sizeProvider?.width || this.props.PanelWidth?.()); - panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.()); + }; + panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.(); + panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.(); screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y); focusDoc = (doc: Doc) => this.props.focus(doc); returnThis = () => this; @@ -163,24 +194,27 @@ export class CollectionFreeFormDocumentView extends DocComponent - {this.props.renderCutoffProvider(this.props.Document) ? -
- : - this._contentView = r)} /> - } -
; + const mixBlendMode = (StrCast(this.layoutDoc.mixBlendMode) as any) || (typeof background === 'string' && background && !background.startsWith('linear') && DashColor(background).alpha() !== 1 ? 'multiply' : undefined); + return ( +
+ {this.props.renderCutoffProvider(this.props.Document) ? ( +
+ ) : ( + (this._contentView = r))} /> + )} +
+ ); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f87581875..f9ef85595 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1640,8 +1640,8 @@ export class DocumentView extends React.Component { render() { TraceMobx(); - const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined); - const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined); + const xshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined); + const yshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined); const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES; const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return ( diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 6e5eb3300..3bbdce1e4 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -29,10 +29,11 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie import { FieldView, FieldViewProps } from '../FieldView'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu'; export interface PinProps { audioRange?: boolean; - setPosition?: boolean; + activeFrame?: number; hidePresBox?: boolean; pinWithView?: PinViewProps; pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document @@ -509,7 +510,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.childDocs.forEach((doc, index) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); - if (tagDoc) tagDoc.opacity = 1; + //if (tagDoc) tagDoc.opacity = 1; const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc); const curInd: number = itemIndexes.indexOf(index); if (tagDoc === this.layoutDoc.presCollection) { @@ -519,7 +520,7 @@ export class PresBox extends ViewBoxBaseComponent() { } else if (curDoc.presHideBefore) { if (index > this.itemIndex) { tagDoc.opacity = 0; - } else if (!curDoc.presHideAfter) { + } else if (index === this.itemIndex || !curDoc.presHideAfter) { tagDoc.opacity = 1; } } @@ -527,7 +528,7 @@ export class PresBox extends ViewBoxBaseComponent() { } else if (curDoc.presHideAfter) { if (index < this.itemIndex) { tagDoc.opacity = 0; - } else if (!curDoc.presHideBefore) { + } else if (index === this.itemIndex || !curDoc.presHideBefore) { tagDoc.opacity = 1; } } @@ -821,17 +822,9 @@ export class PresBox extends ViewBoxBaseComponent() { if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); else this.updateCurrentPresentation(context); - if (this.activeItem.setPosition && this.activeItem.y !== undefined && this.activeItem.x !== undefined && this.targetDoc.x !== undefined && this.targetDoc.y !== undefined) { - const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms))); - const time = 10; - const ydiff = NumCast(this.activeItem.y) - NumCast(this.targetDoc.y); - const xdiff = NumCast(this.activeItem.x) - NumCast(this.targetDoc.x); - - for (let i = 0; i < time; i++) { - this.targetDoc.x = NumCast(this.targetDoc.x) + xdiff / time; - this.targetDoc.y = NumCast(this.targetDoc.y) + ydiff / time; - await timer(0.1); - } + if (this.activeItem.presActiveFrame !== undefined) { + const context = DocCast(DocCast(this.activeItem.presentationTargetDoc).context); + context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(this.activeItem.presActiveFrame)); } }; -- cgit v1.2.3-70-g09d2 From 820d40eea4eeb5977889e0ef6c35f9092df44b4b Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 10 Aug 2022 17:08:47 -0400 Subject: created placeholder loading box but have to get location of final doc to update with loading box --- src/client/documents/DocumentTypes.ts | 3 ++- src/client/documents/Documents.ts | 18 +++++++++++++ src/client/views/collections/CollectionSubView.tsx | 8 ++++-- src/client/views/nodes/DocumentContentsView.tsx | 2 ++ src/client/views/nodes/LoadingBox.scss | 7 +++++ src/client/views/nodes/LoadingBox.tsx | 31 ++++++++++++++++++++++ 6 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 src/client/views/nodes/LoadingBox.scss create mode 100644 src/client/views/nodes/LoadingBox.tsx (limited to 'src/client/views/nodes') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 9dfadf778..d99cd2dac 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -26,6 +26,7 @@ export enum DocumentType { FUNCPLOT = 'funcplot', // function plotter MAP = 'map', DATAVIZ = 'dataviz', + LOADING = 'loading', // special purpose wrappers that either take no data or are compositions of lower level types LINK = 'link', @@ -63,5 +64,5 @@ export enum CollectionViewType { Grid = 'grid', Pile = 'pileup', StackedTimeline = 'stacked timeline', - NoteTaking = "notetaking" + NoteTaking = 'notetaking', } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c7ea04839..6ccb4358a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -48,6 +48,7 @@ import { KeyValueBox } from '../views/nodes/KeyValueBox'; import { LabelBox } from '../views/nodes/LabelBox'; import { LinkBox } from '../views/nodes/LinkBox'; import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup'; +import { LoadingBox } from '../views/nodes/LoadingBox'; import { MapBox } from '../views/nodes/MapBox/MapBox'; import { PDFBox } from '../views/nodes/PDFBox'; import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox'; @@ -648,6 +649,13 @@ export namespace Docs { options: { _fitWidth: true, nativeDimModifiable: true, links: '@links(self)' }, }, ], + [ + DocumentType.LOADING, + { + layout: { view: LoadingBox, dataField: defaultDataKey }, + options: { _fitWidth: true, _fitHeight: true, nativeDimModifiable: true, links: '@links(self)' }, + }, + ], ]); const suffix = 'Proto'; @@ -875,6 +883,16 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } + export function LoadingDocument(title: string, text: string, width?: number, height?: number, options: DocumentOptions = {}) { + let myWidth = 300; + let myHeight = 300; + if (height && width) { + myWidth = width; + myHeight = height; + } + return InstanceFromProto(Prototypes.get(DocumentType.LOADING), '', { ...options, title, text, _width: myWidth, _height: myHeight }); + } + export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 2f3f57bac..79f629072 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -462,7 +462,8 @@ export function CollectionSubView(moreProps?: X) { let placeholders: Doc[] = []; // handle yt case if (typeof files === 'string') { - placeholders.push(Docs.Create.TextDocument('Loading: ' + text, { ...options, title: text, _width: 400, _height: 30 })); + placeholders.push(Docs.Create.LoadingDocument('Loading...', text, 500, 500, { ...options })); + // placeholders.push(Docs.Create.TextDocument('Loading: ' + text, { ...options, title: text, _width: 400, _height: 30 })); } else { // every other doc type is an array of File @@ -471,7 +472,9 @@ export function CollectionSubView(moreProps?: X) { files.forEach(file => { textStr += file.name + '\n'; }); - placeholders.push(Docs.Create.TextDocument('Loading: \n' + textStr, { ...options, title: files.length + ' files', _width: 500, _height: files.length * 40 })); + placeholders.push(Docs.Create.LoadingDocument('Loading...', textStr, 500, 500, { ...options })); + + // placeholders.push(Docs.Create.TextDocument('Loading: \n' + textStr, { ...options, title: files.length + ' files', _width: 500, _height: files.length * 40 })); } // disposer action to remove placeholders once files are uploaded const remove = action(() => { @@ -498,6 +501,7 @@ export function CollectionSubView(moreProps?: X) { clientY: number, addDocument: (doc: Doc | Doc[]) => boolean ) => { + // TODO: once loading thing is moved it should update the x and y of the file it is placeholder for const disposer = this.placeHolderDisposer(files, options, text); // const disposer = OverlayView.Instance.addElement(, { x: clientX - 125, y: clientY - 125 }); if (typeof files === 'string') { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 381436a56..4d4985f2a 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -43,6 +43,7 @@ import { VideoBox } from './VideoBox'; import { WebBox } from './WebBox'; import React = require('react'); import XRegExp = require('xregexp'); +import { LoadingBox } from './LoadingBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -267,6 +268,7 @@ export class DocumentContentsView extends React.Component< DataVizBox, HTMLtag, ComparisonBox, + LoadingBox, }} bindings={bindings} jsx={layoutFrame} diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss new file mode 100644 index 000000000..239faa78e --- /dev/null +++ b/src/client/views/nodes/LoadingBox.scss @@ -0,0 +1,7 @@ +.loadingBoxContainer { + display: flex; + flex-direction: column; + align-content: center; + justify-content: center; + background-color: #fdfdfd; +} diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx new file mode 100644 index 000000000..0e0619241 --- /dev/null +++ b/src/client/views/nodes/LoadingBox.tsx @@ -0,0 +1,31 @@ +import { observer } from 'mobx-react'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; +import { FieldView, FieldViewProps } from './FieldView'; +import * as React from 'react'; +import './LoadingBox.scss'; +import ReactLoading from 'react-loading'; + +export interface LoadingBoxProps { + title: string; + text: string; +} + +@observer +export class LoadingBox extends ViewBoxAnnotatableComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(LoadingBox, fieldKey); + } + + constructor(props: any) { + super(props); + } + + render() { + return ( +
+ Loading: {this.dataDoc.text} + +
+ ); + } +} -- cgit v1.2.3-70-g09d2 From 081632757af3c2ec2e4482c1e5fe710c8ee6cad8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 11 Aug 2022 19:01:37 -0400 Subject: added better ui and backend support for mask ink strokes. added frame #'s ui for seeing current animation frame. --- src/client/documents/Documents.ts | 19 ++++++++-- src/client/util/CurrentUserUtils.ts | 6 ++-- src/client/util/DragManager.ts | 14 ++++---- src/client/views/InkingStroke.tsx | 28 +++++++++++---- src/client/views/MainView.tsx | 1 + src/client/views/collections/CollectionMenu.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 10 +++--- .../CollectionFreeFormLayoutEngines.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.scss | 23 +++++++++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 9 +++-- src/client/views/global/globalCssVariables.scss | 13 +++---- .../views/global/globalCssVariables.scss.d.ts | 6 ++-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 7 ++-- src/client/views/nodes/DocumentView.tsx | 6 ++-- src/client/views/nodes/button/FontIconBox.tsx | 34 +++++++++++++----- src/client/views/nodes/trails/PresBox.tsx | 36 +++++++++++-------- src/fields/InkField.ts | 40 +++++++++++----------- 17 files changed, 162 insertions(+), 93 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c7ea04839..cf9ed43e1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -31,7 +31,7 @@ import { CollectionView } from '../views/collections/CollectionView'; import { ContextMenu } from '../views/ContextMenu'; import { ContextMenuProps } from '../views/ContextMenuItem'; import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from '../views/InkingStroke'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke'; import { AudioBox } from '../views/nodes/AudioBox'; import { FontIconBox } from '../views/nodes/button/FontIconBox'; import { ColorBox } from '../views/nodes/ColorBox'; @@ -918,9 +918,22 @@ export namespace Docs { return linkDoc; } - export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], options: DocumentOptions = {}) { + export function InkDocument( + color: string, + tool: string, + strokeWidth: number, + strokeBezier: string, + fillColor: string, + arrowStart: string, + arrowEnd: string, + dash: string, + points: PointData[], + isInkMask: boolean, + options: DocumentOptions = {} + ) { const I = new Doc(); I[Initializing] = true; + I.isInkMask = isInkMask; I.type = DocumentType.INK; I.layout = InkingStroke.LayoutString('data'); I.color = color; @@ -1424,7 +1437,7 @@ export namespace DocUtils { created = Docs.Create.RecordingDocument(field.url.href, resolved); layout = RecordingBox.LayoutString; } else if (field instanceof InkField) { - created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, resolved); + created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { created = Docs.Create.StackingDocument(DocListCast(field), resolved); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d19874720..dcf4a71c8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -645,8 +645,9 @@ export class CurrentUserUtils { // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:'{ return setActiveTool("circle", _readOnly_);}'} }, // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick: '{ return setActiveTool("line", _readOnly_);}' }}, - { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: "{ return setFillColor(value, _readOnly_);}"} }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:'{ return setActiveTool("line", _readOnly_);}' }}, + { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} }, + { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, ]; @@ -674,6 +675,7 @@ export class CurrentUserUtils { CollectionViewType.Grid, CollectionViewType.NoteTaking]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, + { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20, scripts: {}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index ccd94c56e..d781a87ab 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -377,14 +377,14 @@ export namespace DragManager { } const rect = ele.getBoundingClientRect(); const scaleX = rect.width / (ele.offsetWidth || rect.width); - const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX; + const scaleY = scaleX; //ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX; elesCont.left = Math.min(rect.left, elesCont.left); elesCont.top = Math.min(rect.top, elesCont.top); elesCont.right = Math.max(rect.right, elesCont.right); elesCont.bottom = Math.max(rect.bottom, elesCont.bottom); - xs.push(rect.left); - ys.push(rect.top); + xs.push(rect.left + (options?.offsetX || 0)); + ys.push(rect.top + (options?.offsetY || 0)); scaleXs.push(scaleX); scaleYs.push(scaleY); Object.assign(dragElement.style, { @@ -401,9 +401,9 @@ export namespace DragManager { transformOrigin: '0 0', width: `${rect.width / scaleX}px`, height: `${rect.height / scaleY}px`, - transform: `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`, + transform: `translate(${xs[0]}px, ${ys[0]}px) scale(${scaleX}, ${scaleY})`, }); - dragLabel.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0) - 20}px)`; + dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; if (docsToDrag.length) { const pdfBox = dragElement.getElementsByTagName('canvas'); @@ -543,8 +543,8 @@ export namespace DragManager { const moveVec = { x: x - lastPt.x, y: y - lastPt.y }; lastPt = { x, y }; - dragLabel.style.transform = `translate(${xs[0] + moveVec.x + (options?.offsetX || 0)}px, ${ys[0] + moveVec.y + (options?.offsetY || 0) - 20}px)`; - dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x) + (options?.offsetX || 0)}px, ${(ys[i] += moveVec.y) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)); + dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)); + dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; }; const upHandler = (e: PointerEvent) => { clearTimeout(startWindowDragTimer); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index e5de7a0c5..2671aea56 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -23,9 +23,9 @@ import React = require('react'); import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, WidthSym } from '../../fields/Doc'; +import { Doc, HeightSym, WidthSym } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; -import { Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils'; import { CognitiveServices } from '../cognitive_services/CognitiveServices'; @@ -42,12 +42,13 @@ import { InkTangentHandles } from './InkTangentHandles'; import { DocComponentView } from './nodes/DocumentView'; import { FieldView, FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { INK_MASK_SIZE } from './global/globalCssVariables.scss'; import './InkStroke.scss'; import Color = require('color'); @observer export class InkingStroke extends ViewBoxBaseComponent() { - static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big) + static readonly MaskDim = INK_MASK_SIZE; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big) public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } @@ -353,9 +354,18 @@ export class InkingStroke extends ViewBoxBaseComponent() { const endMarker = StrCast(this.layoutDoc.strokeEndMarker); const markerScale = NumCast(this.layoutDoc.strokeMarkerScale, 1); const closed = InkingStroke.IsClosed(inkData); - const fillColor = StrCast(this.layoutDoc.fillColor, 'transparent'); + const isInkMask = BoolCast(this.layoutDoc.isInkMask); + const fillColor = isInkMask ? '#aaaaaa' : StrCast(this.layoutDoc.fillColor, 'transparent'); const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color); + // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be. + if (isInkMask && (this.layoutDoc[WidthSym]() !== Math.round(this.layoutDoc[WidthSym]()) || this.layoutDoc[HeightSym]() !== Math.round(this.layoutDoc[HeightSym]()))) { + setTimeout(() => { + this.layoutDoc._width = Math.round(NumCast(this.layoutDoc[WidthSym]())); + this.layoutDoc._height = Math.round(NumCast(this.layoutDoc[HeightSym]())); + }); + } + // Visually renders the polygonal line made by the user. const inkLine = InteractionUtils.CreatePolyline( inkData, @@ -430,7 +440,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { className="inkStroke" style={{ transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined, - mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset', + // mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset', cursor: this.props.isSelected() ? 'default' : undefined, }} {...(!closed ? interactions : {})}> @@ -467,7 +477,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { className="inkStroke" style={{ transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined, - mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset', + mixBlendMode: 'unset', cursor: this.props.isSelected() ? 'default' : undefined, position: 'absolute', }} @@ -489,6 +499,9 @@ export function SetActiveBezierApprox(bezier: string): void { export function SetActiveInkColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeInkColor = value); } +export function SetActiveIsInkMask(value: boolean) { + ActiveInkPen() && (ActiveInkPen().activeIsInkMask = value); +} export function SetActiveFillColor(value: string) { ActiveInkPen() && (ActiveInkPen().activeFillColor = value); } @@ -513,6 +526,9 @@ export function ActiveInkColor(): string { export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ''); } +export function ActiveIsInkMask(): boolean { + return BoolCast(ActiveInkPen()?.activeIsInkMask, false); +} export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ''); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e96f65548..c166594e5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -457,6 +457,7 @@ export class MainView extends React.Component { fa.faVolumeDown, fa.faSquareRootAlt, fa.faVolumeMute, + fa.faUserCircle, ] ); this.initAuthenticationRouters(); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 8432619de..eb55650e4 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1025,7 +1025,7 @@ export class CollectionFreeFormViewChrome extends React.Component
- Toggle View All
} placement="bottom"> + Frame number
} placement="bottom">
{ pinDoc.presActiveFrame = pinProps?.activeFrame; pinDoc.title = doc.title + ' (move)'; pinDoc.presMovement = PresMovement.Pan; - if (pinDoc.isInkMask) { - pinDoc.presHideAfter = true; - pinDoc.presHideBefore = true; - pinDoc.presMovement = PresMovement.None; - } + } + if (pinDoc.isInkMask) { + pinDoc.presHideAfter = true; + pinDoc.presHideBefore = true; + pinDoc.presMovement = PresMovement.None; } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; PresBox.Instance?._selectedArray.clear(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 3d85d32a0..ee01c341b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -45,6 +45,7 @@ export interface PoolData { export interface ViewDefResult { ele: JSX.Element; bounds?: ViewDefBounds; + inkMask?: boolean; } function toLabel(target: FieldResult) { if (typeof target === 'number' || Number(target)) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 79e063f7f..010132aa5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,4 +1,4 @@ -@import "../../global/globalCssVariables"; +@import '../../global/globalCssVariables'; .collectionfreeformview-none { position: inherit; @@ -20,8 +20,19 @@ pointer-events: none; } +.collectionfreeformview-mask { + mix-blend-mode: multiply; + z-index: 5000; + width: $INK_MASK_SIZE; + height: $INK_MASK_SIZE; + transform: translate($INK_MASK_SIZE_HALF, $INK_MASK_SIZE_HALF); + background-color: rgba(0, 0, 0, 0.7); + pointer-events: none; + position: absolute; +} + .collectionfreeformview-viewdef { - >.collectionFreeFormDocumentView-container { + > .collectionFreeFormDocumentView-container { pointer-events: none; .contentFittingDocumentDocumentView-previewDoc { @@ -210,13 +221,13 @@ } } - .collectionfreeformview>.jsx-parser { + .collectionfreeformview > .jsx-parser { position: inherit; height: 100%; width: 100%; } - >.jsx-parser { + > .jsx-parser { z-index: 0; } @@ -268,6 +279,6 @@ .pullpane-indicator { z-index: 99999; - background-color: rgba($color: #000000, $alpha: .4); + background-color: rgba($color: #000000, $alpha: 0.4); position: absolute; -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 82b377dfa..f3074543b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -34,7 +34,7 @@ import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariable import { Timeline } from '../../animationtimeline/Timeline'; import { ContextMenu } from '../../ContextMenu'; import { GestureOverlay } from '../../GestureOverlay'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; @@ -121,7 +121,10 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); + const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask).map(ele => ele.ele); + const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && !ele.inkMask).map(ele => ele.ele); + if (viewsMask.length) renderableEles.push(
{viewsMask}
); + return renderableEles; } @computed get fitToContentVals() { return { @@ -579,6 +582,7 @@ export class CollectionFreeFormView extends CollectionSubView { render() { TraceMobx(); - const xshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined); - const yshift = () => (this.props.Document.isInkMask && !this.props.LayoutTemplateString && !this.props.LayoutTemplate?.() ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined); + const xshift = () => (Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined); + const yshift = () => (Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined); const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES; const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return ( @@ -1652,7 +1652,7 @@ export class DocumentView extends React.Component { ref={this.ContentRef} style={{ transition: this.props.dataTransition, - position: this.props.Document.isInkMask ? 'absolute' : undefined, + //position: this.props.Document.isInkMask ? 'absolute' : undefined, transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, height: diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index d3b95e25a..cb68c1ac3 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -1,6 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; +import Color from 'color'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -21,7 +22,7 @@ import { DocComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { GestureOverlay } from '../../GestureOverlay'; import { Colors } from '../../global/globalEnums'; -import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; +import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../../InkingStroke'; import { InkTranscription } from '../../InkTranscription'; import { StyleProp } from '../../StyleProvider'; import { FieldView, FieldViewProps } from '.././FieldView'; @@ -498,8 +499,6 @@ export class FontIconBox extends DocComponent() {
); - const buttonText = StrCast(this.rootDoc.buttonText); - // TODO:glr Add label of button type let button: JSX.Element | null = this.defaultButton; @@ -508,7 +507,7 @@ export class FontIconBox extends DocComponent() { button = (
- {buttonText ?
{buttonText}
: null} + {StrCast(this.rootDoc.buttonText) ?
{StrCast(this.rootDoc.buttonText)}
: null} {label}
); @@ -563,11 +562,13 @@ export class FontIconBox extends DocComponent() { break; } - return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? ( - button - ) : button !== null ? ( - {StrCast(this.layoutDoc.toolTip)}
}>{button} - ) : null; + const retval = + !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? ( + button + ) : button !== null ? ( + {StrCast(this.layoutDoc.toolTip)}
}>{button} + ) : null; + return retval; } } @@ -860,6 +861,21 @@ ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) } }); +// toggle: Set overlay status of selected document +ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) { + const selected = SelectionManager.Docs().lastElement(); + if (checkResult) { + if (selected?.type === DocumentType.INK) { + return BoolCast(selected.isInkMask) ? Colors.MEDIUM_BLUE : 'transparent'; + } + return ActiveIsInkMask() ? Colors.MEDIUM_BLUE : 'transparent'; + } + SetActiveIsInkMask(!ActiveIsInkMask()); + SelectionManager.Docs() + .filter(doc => doc.type === DocumentType.INK) + .map(doc => (doc.isInkMask = !doc.isInkMask)); +}); + // toggle: Set overlay status of selected document ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 3bbdce1e4..627f35e71 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -313,10 +313,15 @@ export class PresBox extends ViewBoxBaseComponent() { //it'll also execute the necessary actions if presentation is playing. public gotoDocument = action((index: number, from?: Doc, group?: boolean) => { Doc.UnBrushAllDocs(); + if (index >= 0 && index < this.childDocs.length) { this.rootDoc._itemIndex = index; const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; + if (activeItem.presActiveFrame !== undefined) { + const context = DocCast(DocCast(activeItem.presentationTargetDoc).context); + context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(activeItem.presActiveFrame)); + } if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) { DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia); } @@ -516,20 +521,26 @@ export class PresBox extends ViewBoxBaseComponent() { if (tagDoc === this.layoutDoc.presCollection) { tagDoc.opacity = 1; } else { - if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) { - } else if (curDoc.presHideBefore) { - if (index > this.itemIndex) { - tagDoc.opacity = 0; - } else if (index === this.itemIndex || !curDoc.presHideAfter) { + if (curDoc.presHideBefore) { + if (itemIndexes.length > 1 && curInd !== 0) { tagDoc.opacity = 1; + } else { + if (index > this.itemIndex) { + tagDoc.opacity = 0; + } else if (index === this.itemIndex || !curDoc.presHideAfter) { + tagDoc.opacity = 1; + } } } - if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== itemIndexes.length - 1) { - } else if (curDoc.presHideAfter) { - if (index < this.itemIndex) { - tagDoc.opacity = 0; - } else if (index === this.itemIndex || !curDoc.presHideBefore) { + if (curDoc.presHideAfter) { + if (itemIndexes.length > 1 && curInd !== itemIndexes.length - 1) { tagDoc.opacity = 1; + } else { + if (index < this.itemIndex) { + tagDoc.opacity = 0; + } else if (index === this.itemIndex || !curDoc.presHideBefore) { + tagDoc.opacity = 1; + } } } } @@ -821,11 +832,6 @@ export class PresBox extends ViewBoxBaseComponent() { this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem); if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); else this.updateCurrentPresentation(context); - - if (this.activeItem.presActiveFrame !== undefined) { - const context = DocCast(DocCast(this.activeItem.presentationTargetDoc).context); - context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(this.activeItem.presActiveFrame)); - } }; //Command click diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 114d5fc2f..a074098c1 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -1,22 +1,21 @@ -import { Bezier } from "bezier-js"; -import { createSimpleSchema, list, object, serializable } from "serializr"; -import { ScriptingGlobals } from "../client/util/ScriptingGlobals"; -import { Deserializable } from "../client/util/SerializationHelper"; -import { Copy, ToScriptString, ToString } from "./FieldSymbols"; -import { ObjectField } from "./ObjectField"; +import { Bezier } from 'bezier-js'; +import { createSimpleSchema, list, object, serializable } from 'serializr'; +import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; +import { Deserializable } from '../client/util/SerializationHelper'; +import { Copy, ToScriptString, ToString } from './FieldSymbols'; +import { ObjectField } from './ObjectField'; // Helps keep track of the current ink tool in use. export enum InkTool { - None = "none", - Pen = "pen", - Highlighter = "highlighter", - Eraser = "eraser", - Stamp = "stamp", - Write = "write", - PresentationPin = 'presentationpin' + None = 'none', + Pen = 'pen', + Highlighter = 'highlighter', + Eraser = 'eraser', + Stamp = 'stamp', + Write = 'write', + PresentationPin = 'presentationpin', } - // Defines a point in an ink as a pair of x- and y-coordinates. export interface PointData { X: number; @@ -54,15 +53,16 @@ export interface HandleLine { } const pointSchema = createSimpleSchema({ - X: true, Y: true + X: true, + Y: true, }); const strokeDataSchema = createSimpleSchema({ pathData: list(object(pointSchema)), - "*": true + '*': true, }); -@Deserializable("ink") +@Deserializable('ink') export class InkField extends ObjectField { @serializable(list(object(strokeDataSchema))) readonly inkData: InkData; @@ -85,11 +85,11 @@ export class InkField extends ObjectField { } [ToScriptString]() { - return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}`) + "])"; + return 'new InkField([' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + '])'; } [ToString]() { - return "InkField"; + return 'InkField'; } } -ScriptingGlobals.add("InkField", InkField); \ No newline at end of file +ScriptingGlobals.add('InkField', InkField); -- cgit v1.2.3-70-g09d2 From 8d99f6aa2672aadb8c5c5ae3cf2449f26a9eb74c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 12 Aug 2022 15:19:53 -0400 Subject: fixed inline text box resizing --- src/client/views/nodes/formattedText/RichTextRules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 1916b94bf..2097b321f 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -80,7 +80,7 @@ export class RichTextRules { textDoc.inlineTextCount = numInlines + 1; const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation - const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: '9px', title: 'inline comment' }); + const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _fitWidth: true, _autoHeight: true, _fontSize: '9px', title: 'inline comment' }); textDocInline.title = inlineFieldKey; // give the annotation its own title textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point -- cgit v1.2.3-70-g09d2 From 0b43c12bf9394f8561de57299c733734082f9f0a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 15 Aug 2022 14:08:38 -0400 Subject: from last - fix for setting dash field numbers --- src/client/views/nodes/formattedText/DashFieldView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index d2f7b5677..35d919f38 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -178,7 +178,7 @@ export class DashFieldViewInternal extends React.Component Date: Tue, 16 Aug 2022 16:43:01 -0400 Subject: updated placeholder --- src/.DS_Store | Bin 10244 -> 10244 bytes src/client/documents/Documents.ts | 63 +++++++++++++------ src/client/util/CurrentUserUtils.ts | 1 + src/client/views/collections/CollectionSubView.tsx | 70 +++++++-------------- src/client/views/nodes/LoadingBox.tsx | 25 ++++++++ 5 files changed, 94 insertions(+), 65 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/.DS_Store b/src/.DS_Store index 4751acf44..4ed785983 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6ccb4358a..d8497e3af 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,4 +1,5 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { files } from 'jszip'; import { action, runInAction } from 'mobx'; import { basename } from 'path'; import { DateField } from '../../fields/DateField'; @@ -794,7 +795,7 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string) { + function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDoc?: Doc) { const viewKeys = ['x', 'y', 'system']; // keys that should be addded to the view document even though they don't begin with an "_" const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_'); @@ -813,13 +814,22 @@ export namespace Docs { // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. dataProps[fieldKey + '-annotations'] = new List(); dataProps[fieldKey + '-sidebar'] = new List(); - const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); + + const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); + + if (placeholderDoc) { + dataDoc.proto = proto; + } const viewFirstProps: { [id: string]: any } = {}; viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; viewFirstProps['acl-Override'] = 'None'; viewFirstProps.author = Doc.CurrentUserEmail; - const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); + let viewDoc: Doc; + if (placeholderDoc) { + viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); + } + viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); Doc.assign(viewDoc, viewProps, true, true); ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); @@ -883,14 +893,12 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - export function LoadingDocument(title: string, text: string, width?: number, height?: number, options: DocumentOptions = {}) { - let myWidth = 300; - let myHeight = 300; - if (height && width) { - myWidth = width; - myHeight = height; - } - return InstanceFromProto(Prototypes.get(DocumentType.LOADING), '', { ...options, title, text, _width: myWidth, _height: myHeight }); + export const filesToDocs = new Map(); + + export function LoadingDocument(file: File, options: DocumentOptions, ytString?: string) { + const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), file.name, { _height: 300, _width: 300, ...options }); + // filesToDocs.set(loading, file); + return loading; } export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { @@ -969,13 +977,13 @@ export namespace Docs { return I; } - export function PdfDocument(url: string, options: DocumentOptions = {}) { + export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { const width = options._width || undefined; const height = options._height || undefined; const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width); - return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(url), options); + return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(url), options, undefined, undefined, undefined, overwriteDoc); } export function WebDocument(url: string, options: DocumentOptions = {}) { @@ -1462,8 +1470,8 @@ export namespace DocUtils { return created; } - export async function DocumentFromType(type: string, path: string, options: DocumentOptions): Promise> { - let ctor: ((path: string, options: DocumentOptions) => Doc | Promise) | undefined = undefined; + export async function DocumentFromType(type: string, path: string, options: DocumentOptions, rootDoc?: Doc): Promise> { + let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise) | undefined = undefined; if (type.indexOf('image') !== -1) { ctor = Docs.Create.ImageDocument; if (!options._width) options._width = 300; @@ -1512,7 +1520,7 @@ export namespace DocUtils { options = { ...options, _width: 400, _height: 512, title: path }; } - return ctor ? ctor(path, options) : undefined; + return ctor ? ctor(path, options, rootDoc) : undefined; } export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void { @@ -1728,14 +1736,14 @@ export namespace DocUtils { return dd; } - async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions) { + async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, rootDoc?: Doc) { if (result instanceof Error) { alert(`Upload failed: ${result.message}`); return; } const full = { ...options, _width: 400, title: name }; const pathname = Utils.prepend(result.accessPaths.agnostic.client); - const doc = await DocUtils.DocumentFromType(type, pathname, full); + const doc = await DocUtils.DocumentFromType(type, pathname, full, rootDoc); if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; @@ -1813,6 +1821,25 @@ export namespace DocUtils { return generatedDocuments; } + export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) { + const generatedDocuments: Doc[] = []; + Networking.UploadFilesToServer([file]).then(upfiles => { + const { + source: { name, type }, + result, + } = upfiles.lastElement(); + console.log(name, type); + name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + }); + } + + export function generatePlaceHolder(file: File, options: DocumentOptions) { + return Docs.Create.LoadingDocument(file, options); + // placeholder.file = file + // TODO: nda - modify loading doc so it only takes in options + // Docs.Create.LoadingDocument(options, ) + } + // copies the specified drag factory document export function copyDragFactory(dragFactory: Doc) { if (!dragFactory) return undefined; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d19874720..492513a61 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -196,6 +196,7 @@ export class CurrentUserUtils { makeIconTemplate(DocumentType.RTF, "text", { iconTemplate:DocumentType.LABEL, _showTitle: "creationDate"}), makeIconTemplate(DocumentType.IMG, "data", { iconTemplate:DocumentType.IMG, _height: undefined}), makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), + makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}), makeIconTemplate(DocumentType.BUTTON,"data", { iconTemplate:DocumentType.FONTICON}), //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 79f629072..1ff4f5ab8 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -449,48 +449,6 @@ export function CollectionSubView(moreProps?: X) { this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); } - /** - * Creates a placeholder doc view for files being uploaded and removes placeholder docs once files are uplodaded. - * - * @param files the files to upload that we want to create placeholders for - * @param options the document options (primarily the x and y coordinates to put doc) - * @param text in the case of youtube the text is the url to the video - * @returns a disposer action that removes the placeholders created after files get uploaded - */ - placeHolderDisposer = (files: File[] | string, options: DocumentOptions, text: string) => { - // TODO: nda - create a specialized view for placeholder upload with a spinner and ability to retry upload - let placeholders: Doc[] = []; - // handle yt case - if (typeof files === 'string') { - placeholders.push(Docs.Create.LoadingDocument('Loading...', text, 500, 500, { ...options })); - // placeholders.push(Docs.Create.TextDocument('Loading: ' + text, { ...options, title: text, _width: 400, _height: 30 })); - } else { - // every other doc type is an array of File - - // Get the file names as a text - let textStr = ''; - files.forEach(file => { - textStr += file.name + '\n'; - }); - placeholders.push(Docs.Create.LoadingDocument('Loading...', textStr, 500, 500, { ...options })); - - // placeholders.push(Docs.Create.TextDocument('Loading: \n' + textStr, { ...options, title: files.length + ' files', _width: 500, _height: files.length * 40 })); - } - // disposer action to remove placeholders once files are uploaded - const remove = action(() => { - if (!this.props.DataDoc) { - return; - } - for (let i = 0; i < placeholders.length; i++) { - Doc.RemoveDocFromList(this.props.DataDoc, 'data', placeholders[i]); - } - }); - placeholders.forEach(pl => { - this.addDocument(pl); - }); - return remove; - }; - slowLoadDocuments = async ( files: File[] | string, options: DocumentOptions, @@ -501,13 +459,31 @@ export function CollectionSubView(moreProps?: X) { clientY: number, addDocument: (doc: Doc | Doc[]) => boolean ) => { + // create placeholder docs + // inside placeholder docs have some func that + // TODO: once loading thing is moved it should update the x and y of the file it is placeholder for - const disposer = this.placeHolderDisposer(files, options, text); + let pileUpDoc = undefined; // const disposer = OverlayView.Instance.addElement(, { x: clientX - 125, y: clientY - 125 }); if (typeof files === 'string') { - generatedDocuments.push(...(await DocUtils.uploadYoutubeVideo(files, options))); + // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder + // generatedDocuments.push(Docs.Create.LoadingDocument(files, options)); } else { - generatedDocuments.push(...(await DocUtils.uploadFilesToDocs(files, options))); + // uploadFilesToDocs and similar should return a placeholder, one for each placeholder + generatedDocuments.push( + ...files.map(file => { + const loading = Docs.Create.LoadingDocument(file, options); + // now that there is a doc do whatever slowload was going to do with that file + if (typeof file === 'string') { + // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder + // (await DocUtils.uploadYoutubeVideo(files, options))); + } else { + // uploadFilesToDocs and similar should return a placeholder, one for each placeholder + DocUtils.uploadFileToDoc(file, {}, loading); + } + return loading; + }) + ); } if (generatedDocuments.length) { // Creating a dash document @@ -523,7 +499,8 @@ export function CollectionSubView(moreProps?: X) { if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { - addDocument(DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!); + pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!; + addDocument(pileUpDoc); } else { generatedDocuments.forEach(addDocument); } @@ -535,7 +512,6 @@ export function CollectionSubView(moreProps?: X) { alert('Document upload failed - possibly an unsupported file type.'); } } - disposer(); }; } diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 0e0619241..9d4668dde 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -4,6 +4,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import * as React from 'react'; import './LoadingBox.scss'; import ReactLoading from 'react-loading'; +import { Docs, DocUtils } from '../../documents/Documents'; export interface LoadingBoxProps { title: string; @@ -16,6 +17,30 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { return FieldView.LayoutString(LoadingBox, fieldKey); } + componentDidMount() { + // const file = Docs.Create.filesToDocs.get(this.rootDoc); + // if (file) { + // console.log('Got to file'); + // Docs.Create.filesToDocs.delete(this.rootDoc); + // // now that there is a doc do whatever slowload was going to do with that file + // if (typeof file === 'string') { + // // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder + // // (await DocUtils.uploadYoutubeVideo(files, options))); + // } else { + // // uploadFilesToDocs and similar should return a placeholder, one for each placeholder + // DocUtils.uploadFileToDoc(file, {}, this.rootDoc); + // } + // } else { + // // check if file now exists on server or not + // // if it does we need to retreieve it and create the appropriate doc (rest of what uploadFileToDoc was doing minus uploading) + // // if it doesn't display an error message "upload failed" + // } + // query endpoints to: + // check if file now exists on server or not + // if it does we need to retreieve it and create the appropriate doc (rest of what uploadFileToDoc was doing minus uploading) + // if it doesn't display an error message "upload failed" + } + constructor(props: any) { super(props); } -- cgit v1.2.3-70-g09d2 From 27945b9a931fa9504404174dd08964556dc3aea2 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Tue, 16 Aug 2022 21:16:03 -0400 Subject: added loading for diff doc types --- src/client/documents/Documents.ts | 47 ++++++++++++++++------ src/client/views/collections/CollectionSubView.tsx | 11 ++--- src/client/views/nodes/LoadingBox.scss | 5 +++ src/client/views/nodes/LoadingBox.tsx | 12 +++--- 4 files changed, 48 insertions(+), 27 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d8497e3af..e54fe16de 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -844,9 +844,9 @@ export namespace Docs { return viewDoc; } - export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}) { + export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) { const imgField = url instanceof ImageField ? url : new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc); } export function PresDocument(options: DocumentOptions = {}) { @@ -857,12 +857,12 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); } - export function VideoDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options); + export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { + return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc); } - export function YoutubeDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options); + export function YoutubeDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { + return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options, undefined, undefined, undefined, overwriteDoc); } export function WebCamDocument(url: string, options: DocumentOptions = {}) { @@ -877,8 +877,16 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), '', options); } - export function AudioDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }); + export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { + return InstanceFromProto( + Prototypes.get(DocumentType.AUDIO), + new AudioField(url), + { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }, + undefined, + undefined, + undefined, + overwriteDoc + ); } export function RecordingDocument(url: string, options: DocumentOptions = {}) { @@ -895,8 +903,10 @@ export namespace Docs { export const filesToDocs = new Map(); - export function LoadingDocument(file: File, options: DocumentOptions, ytString?: string) { - const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), file.name, { _height: 300, _width: 300, ...options }); + export function LoadingDocument(file: File | string, options: DocumentOptions, ytString?: string) { + const fileName = typeof file == 'string' ? file : file.name; + options.title = fileName; + const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), fileName, { _height: 150, _width: 150, ...options }); // filesToDocs.set(loading, file); return loading; } @@ -1114,8 +1124,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } - export function DataVizDocument(url: string, options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }); + export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) { + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }, undefined, undefined, undefined, overwriteDoc); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { @@ -1809,6 +1819,18 @@ export namespace DocUtils { } return generatedDocuments; } + + export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) { + const generatedDocuments: Doc[] = []; + Networking.UploadYoutubeToServer(videoId).then(upfiles => { + const { + source: { name, type }, + result, + } = upfiles.lastElement(); + name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + }); + } + export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { const generatedDocuments: Doc[] = []; const upfiles = await Networking.UploadFilesToServer(files); @@ -1828,7 +1850,6 @@ export namespace DocUtils { source: { name, type }, result, } = upfiles.lastElement(); - console.log(name, type); name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); }); } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 1ff4f5ab8..30467efa0 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -468,19 +468,16 @@ export function CollectionSubView(moreProps?: X) { if (typeof files === 'string') { // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder // generatedDocuments.push(Docs.Create.LoadingDocument(files, options)); + const loading = Docs.Create.LoadingDocument(files, options); + generatedDocuments.push(loading); + DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { // uploadFilesToDocs and similar should return a placeholder, one for each placeholder generatedDocuments.push( ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); // now that there is a doc do whatever slowload was going to do with that file - if (typeof file === 'string') { - // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder - // (await DocUtils.uploadYoutubeVideo(files, options))); - } else { - // uploadFilesToDocs and similar should return a placeholder, one for each placeholder - DocUtils.uploadFileToDoc(file, {}, loading); - } + DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) ); diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss index 239faa78e..e8890cd82 100644 --- a/src/client/views/nodes/LoadingBox.scss +++ b/src/client/views/nodes/LoadingBox.scss @@ -5,3 +5,8 @@ justify-content: center; background-color: #fdfdfd; } + +.text { + text-overflow: ellipsis; + text-align: center; +} diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 9d4668dde..96620aff9 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -4,12 +4,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import * as React from 'react'; import './LoadingBox.scss'; import ReactLoading from 'react-loading'; -import { Docs, DocUtils } from '../../documents/Documents'; - -export interface LoadingBoxProps { - title: string; - text: string; -} +import { StrCast } from '../../../fields/Types'; @observer export class LoadingBox extends ViewBoxAnnotatableComponent() { @@ -18,6 +13,7 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { } componentDidMount() { + console.log(this.rootDoc); // const file = Docs.Create.filesToDocs.get(this.rootDoc); // if (file) { // console.log('Got to file'); @@ -48,7 +44,9 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { render() { return (
- Loading: {this.dataDoc.text} +

Loading:

+

+

{StrCast(this.rootDoc.title)}

); -- cgit v1.2.3-70-g09d2 From 0178de4ab9ffd11630b700f9c02468b74beabd14 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 17 Aug 2022 12:31:26 -0400 Subject: fixed dragging docs on web and pdf to work better by temporarily adding transparent docs to the opaque layer so they can get drop events. cleaned eraser and pen interaction code and made erasing strokes work faster and avoid hanging by not intersecting strokes that are already partially deleted. --- src/Utils.ts | 3 +- src/client/documents/Documents.ts | 26 +--- src/client/views/collections/CollectionSubView.tsx | 12 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 140 ++++++--------------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/VideoBox.tsx | 11 +- src/client/views/nodes/WebBox.scss | 17 +-- src/client/views/nodes/WebBox.tsx | 110 ++++++++-------- src/client/views/pdf/PDFViewer.tsx | 75 +++++------ 10 files changed, 153 insertions(+), 252 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/Utils.ts b/src/Utils.ts index 528a429d0..9e002ebd4 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -111,6 +111,7 @@ export namespace Utils { const isTransparentFunctionHack = 'isTransparent(__value__)'; export const noRecursionHack = '__noRecursion'; + export const noDragsDocFilter = 'noDragDocs:any:check'; export function IsRecursiveFilter(val: string) { return !val.includes(noRecursionHack); } @@ -125,7 +126,7 @@ 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) { + export function IsPropUnsetFilter(prop: string) { return `${prop}:any,${noRecursionHack}:unset`; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cf9ed43e1..e579bfd8a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1143,30 +1143,6 @@ export namespace Docs { } export namespace DocUtils { - export function Excluded(d: Doc, docFilters: string[]) { - const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields - docFilters.forEach(filter => { - const fields = filter.split(':'); - const key = fields[0]; - const value = fields[1]; - const modifiers = fields[2]; - if (!filterFacets[key]) { - filterFacets[key] = {}; - } - filterFacets[key][value] = modifiers; - }); - - if (d.z) return false; - for (const facetKey of Object.keys(filterFacets)) { - const facet = filterFacets[facetKey]; - const xs = Object.keys(facet).filter(value => facet[value] === 'x'); - const failsNotEqualFacets = xs?.some(value => Doc.matchFieldValue(d, facetKey, value)); - if (failsNotEqualFacets) { - return true; - } - } - return false; - } /** * @param docs * @param docFilters @@ -1200,7 +1176,7 @@ export namespace DocUtils { return false; } - for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies')) { + for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(':')[0])) { const facet = filterFacets[facetKey]; // facets that match some value in the field of the document (e.g. some text field) diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5479929bd..e33bb77de 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -88,11 +88,11 @@ export function CollectionSubView(moreProps?: X) { } collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._docFilters); collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec('string'), []); + // child filters apply to the descendants of the documents in this collection childDocFilters = () => [...(this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; + // unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack unrecursiveDocFilters = () => [...(this.props.docFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])]; childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()]; - IsFiltered = () => - this.collectionFilters().length || this.collectionRangeDocFilters().length ? 'hasFilter' : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? 'inheritsFilter' : undefined; searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs); @computed.struct get childDocs() { TraceMobx(); @@ -122,13 +122,11 @@ export function CollectionSubView(moreProps?: X) { return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one } - // console.log(Doc.ActiveDashboard._docFilters); - // if (!this.props.Document._docFilters && this.props.Document.currentFilter) { - // (this.props.Document.currentFilter as Doc).filterBoolean = (this.props.ContainingCollectionDoc?.currentFilter as Doc)?.filterBoolean; - // } const docsforFilter: Doc[] = []; childDocs.forEach(d => { - // if (DocUtils.Excluded(d, docFilters)) return; + // dragging facets + const dragged = this.props.docFilters?.().some(f => f.includes(Utils.noDragsDocFilter)); + if (dragged && DragManager.docsBeingDragged.includes(d)) return false; let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0; if (notFiltered) { notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f3074543b..45a5e30ff 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -109,8 +109,6 @@ export class CollectionFreeFormView extends CollectionSubView, props: Opt, property: string) => { let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 - if (property !== StyleProp.BackgroundColor) return styleProp; - const cluster = NumCast(doc?.cluster); - if (this.Document._useClusters) { - if (this._clusterSets.length <= cluster) { - setTimeout(() => doc && this.updateCluster(doc)); - } else { - // choose a cluster color from a palette - const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; - styleProp = colors[cluster % colors.length]; - const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); - // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.map(s => (styleProp = StrCast(s.backgroundColor))); - } - } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; + switch (property) { + case StyleProp.BackgroundColor: + const cluster = NumCast(doc?.cluster); + if (this.Document._useClusters) { + if (this._clusterSets.length <= cluster) { + setTimeout(() => doc && this.updateCluster(doc)); + } else { + // choose a cluster color from a palette + const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; + styleProp = colors[cluster % colors.length]; + const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); + // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document + set?.map(s => (styleProp = StrCast(s.backgroundColor))); + } + } + break; + } return styleProp; }; @@ -523,17 +524,13 @@ export class CollectionFreeFormView extends CollectionSubView { - if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener('pointermove', this.onEraserMove); - document.removeEventListener('pointerup', this.onEraserUp); - this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc)); - this._deleteList = []; - this._batch?.end(); - } - }; - - @action - onPointerUp = (e: PointerEvent): void => { - if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); - this.removeMoveListeners(); - this.removeEndListeners(); - } + this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc)); + this._deleteList = []; + this._batch?.end(); }; onClick = (e: React.MouseEvent) => { @@ -752,46 +735,42 @@ export class CollectionFreeFormView extends CollectionSubView { + onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => { const currPoint = { X: e.clientX, Y: e.clientY }; - this.getEraserIntersections({ X: this._lastX, Y: this._lastY }, currPoint).forEach(intersect => { + this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => { if (!this._deleteList.includes(intersect.inkView)) { this._deleteList.push(intersect.inkView); SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1'); SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black'); // create a new curve by appending all curves of the current segment together in order to render a single new stroke. - !e.shiftKey && + if (!e.shiftKey) { this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment => GestureOverlay.Instance.dispatchGesture( GestureUtils.Gestures.Stroke, segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) ) ); + } // Lower ink opacity to give the user a visual indicator of deletion. intersect.inkView.layoutDoc.opacity = 0.5; + intersect.inkView.layoutDoc.dontIntersect = true; } }); - this._lastX = currPoint.X; - this._lastY = currPoint.Y; - - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); + return false; }; @action - onPointerMove = (e: PointerEvent): void => { - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; + onPointerMove = (e: PointerEvent): boolean => { + if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { Doc.ActiveTool = InkTool.None; if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { if (this.tryDragCluster(e, this._hitCluster)) { - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); + return true; } else this.pan(e); - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); } + return false; }; /** @@ -801,6 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView { const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) }; const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) }; + return this.childDocs .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) @@ -888,7 +868,7 @@ export class CollectionFreeFormView extends CollectionSubView doc.type === DocumentType.INK) + .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect) .forEach(doc => { const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke; const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; @@ -919,7 +899,6 @@ export class CollectionFreeFormView extends CollectionSubView only want to do this when collection is selected' @@ -967,13 +946,6 @@ export class CollectionFreeFormView extends CollectionSubView { - switch (this._pullDirection) { - case 'left': - case 'right': - case 'top': - case 'bottom': - CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: 'New Collection' }), this._pullDirection); - } - - this._pullDirection = ''; - this._pullCoords = [0, 0]; - - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); }; @@ -1380,7 +1321,7 @@ export class CollectionFreeFormView extends CollectionSubView -
+
{this.layoutDoc._backgroundGridShow ? (
} -
{ // uncomment to show snap lines
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 78f351f4f..e19e2d525 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable, trace } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; @@ -6,13 +6,12 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DashColor, numberRange, OmitKeys } from '../../../Utils'; +import { numberRange } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { DocComponent } from '../DocComponent'; -import { InkingStroke } from '../InkingStroke'; import { StyleProp } from '../StyleProvider'; import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps } from './DocumentView'; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bcc55eab4..172adcafe 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -970,7 +970,11 @@ 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; + return this.collectionFilters().length || this.collectionRangeDocFilters().length + ? 'hasFilter' + : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).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; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index a3d501153..0ff15f93b 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -831,16 +831,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent 1) { - return { height: '100%' }; - } else { - return { width: '100%' }; + //prettier-ignore + return ({ height: '100%' }); } + //prettier-ignore + return ({ width: '100%' }); } // for zoom slider, sets timeline waveform zoom - zoom = (zoom: number) => { - this.timeline?.setZoom(zoom); - }; + zoom = (zoom: number) => this.timeline?.setZoom(zoom); // plays link playLink = (doc: Doc) => { diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index d8dd074a5..85986ff27 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -1,9 +1,11 @@ -@import "../global/globalCssVariables.scss"; - +@import '../global/globalCssVariables.scss'; .webBox { height: 100%; - position: relative; + width: 100%; + top: 0; + left: 0; + position: absolute; display: flex; .webBox-sideResizer { @@ -84,7 +86,6 @@ background: none; } - .webBox-overlayCont { position: absolute; width: calc(100% - 40px); @@ -95,7 +96,7 @@ justify-content: center; align-items: center; overflow: hidden; - transition: left .5s; + transition: left 0.5s; pointer-events: all; .webBox-searchBar { @@ -158,7 +159,7 @@ left: 0; cursor: text; padding: 15px; - height: 100% + height: 100%; } .webBox-cont { @@ -235,7 +236,7 @@ height: 25px; align-items: center; - >svg { + > svg { margin: auto; } } @@ -257,4 +258,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ca9f363c1..6c2e42f86 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as WebRequest from 'web-request'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; @@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; @@ -35,6 +35,7 @@ import { LinkDocPreview } from './LinkDocPreview'; import { VideoBox } from './VideoBox'; import './WebBox.scss'; import React = require('react'); +import { DragManager } from '../../util/DragManager'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -818,6 +819,56 @@ export class WebBox extends ViewBoxAnnotatableComponent string[]) => ( + + ); + return ( +
this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} + onPointerDown={this.onMarqueeDown}> +
+ {this.content} + {
{renderAnnotations(this.transparentFilter)}
} + {renderAnnotations(this.opaqueFilter)} + {this.annotationLayer} +
+
+ ); + } + @computed get searchUI() { return (
e.stopPropagation()} style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}> @@ -859,9 +910,8 @@ export class WebBox extends ViewBoxAnnotatableComponent this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 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; - basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')]; transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; childStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (doc.textInlineAnnotations) return 'none'; @@ -871,37 +921,11 @@ export class WebBox extends ViewBoxAnnotatableComponent (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'); annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() ? 'all' : 'none'); render() { - const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; + const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); const scale = previewScale * (this.props.NativeDimScaling?.() || 1); - const renderAnnotations = (docFilters?: () => string[]) => ( - - ); return ( -
+
-
{ - e.stopPropagation(); - e.preventDefault(); - }} // block wheel events from propagating since they're handled by the iframe - onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} - onPointerDown={this.onMarqueeDown}> -
- {this.content} -
{renderAnnotations(this.transparentFilter)}
- {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? null : renderAnnotations()} - {this.annotationLayer} -
-
+ {this.webpage} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
{ overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1)); panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); - basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')]; transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; childStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (doc.textInlineAnnotations) return 'none'; @@ -498,56 +498,43 @@ export class PDFViewer extends React.Component { return this.props.styleProvider?.(doc, props, property); }; - renderAnnotations = (docFilters?: () => string[], dontRender?: boolean) => ( - + renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => ( +
+ +
); @computed get overlayTransparentAnnotations() { - return this.renderAnnotations(this.transparentFilter, false); + return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length ? 'none' : undefined); } @computed get overlayOpaqueAnnotations() { - return this.renderAnnotations(this.opaqueFilter, false); - } - @computed get overlayClickableAnnotations() { - return
{this.renderAnnotations(undefined, true)}
; + return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined); } @computed get overlayLayer() { return (
-
- {this.overlayTransparentAnnotations} -
-
anno.mixBlendMode) ? 'hard-light' : undefined, - transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`, - }}> - {this.overlayOpaqueAnnotations} - {this.overlayClickableAnnotations} -
+ {this.overlayTransparentAnnotations} + {this.overlayOpaqueAnnotations}
); } -- cgit v1.2.3-70-g09d2 From 5a425e5cf18115921ecb4e7cf931e65f45dab8e2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Aug 2022 11:18:14 -0400 Subject: fixed up presboxelement to not reference presbox.instance. fixing layout of presboxelement in treeview. fixing "removing" an inkMask without removing it from the collection so that it can be turned on and off. --- src/client/util/CurrentUserUtils.ts | 9 +- src/client/views/DocumentDecorations.tsx | 6 +- src/client/views/PropertiesView.tsx | 5 +- .../views/collections/CollectionTreeView.scss | 11 +- src/client/views/collections/TabDocView.tsx | 4 +- src/client/views/collections/TreeView.scss | 17 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/nodes/trails/PresBox.tsx | 137 +++++++++------------ src/client/views/nodes/trails/PresElementBox.tsx | 73 ++++++----- 9 files changed, 132 insertions(+), 131 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index dcf4a71c8..f7d072d80 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -289,11 +289,12 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, }, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'} }, { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, funcs: { hidden: 'IsNoviceMode()'}}, { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, }, - { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, - { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, + { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc,scripts: { onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, + { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: { onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, ].map(tuple => ({scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, ...tuple, })) } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3544f74b4..6d1397395 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -203,7 +203,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (this._deleteAfterIconify) { views.forEach(iconView => { Doc.setNativeView(iconView.props.Document); - iconView.props.removeDocument?.(iconView.props.Document); + if (iconView.props.Document.isInkMask && iconView.props.Document.activeFrame !== undefined) { + iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMaks to be "turned off" without removing them from the collection which allows them to function properly in a presenation. + } else { + iconView.props.removeDocument?.(iconView.props.Document); + } }); SelectionManager.DeselectAll(); } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index ef0e057dc..33f17047b 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -15,7 +15,7 @@ import { ComputedField } from '../../fields/ScriptField'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from '../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; -import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; +import { DocumentType } from '../documents/DocumentTypes'; import { DocumentManager } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; @@ -1614,9 +1614,6 @@ export class PropertiesView extends React.Component { if (this.isPres) { const selectedItem: boolean = PresBox.Instance?._selectedArray.size > 0; const type = PresBox.Instance.activeItem?.type; - const viewType = PresBox.Instance.activeItem?._viewType; - const pannable: boolean = (type === DocumentType.COL && viewType === CollectionViewType.Freeform) || type === DocumentType.IMG; - const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking || viewType === CollectionViewType.NoteTaking; return (
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 93523a6cf..c0561e42c 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,6 +1,5 @@ -@import "../global/globalCssVariables"; +@import '../global/globalCssVariables'; - .collectionTreeView-container { transform-origin: top left; height: 100%; @@ -28,7 +27,7 @@ list-style: none; padding-left: $TREE_BULLET_WIDTH; margin-bottom: 1px; // otherwise vertical scrollbars may pop up for no apparent reason.... - > .contentFittingDocumentView { + > .contentFittingDocumentView { width: unset; height: unset; } @@ -39,7 +38,7 @@ .no-indent { padding-left: 0; - width: max-content; + //width: max-content; } .no-indent-outline { @@ -85,7 +84,7 @@ width: 100%; height: max-content; .contentFittingDocumentView { - display: block; // makes titleBar take up full width of the treeView (flex doesn't for some reason) + display: block; // makes titleBar take up full width of the treeView (flex doesn't for some reason) } } @@ -114,4 +113,4 @@ padding-left: 3px; padding-right: 3px; padding-bottom: 2px; -} \ No newline at end of file +} diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index ff4b1e2ce..e147f34d2 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -299,8 +299,8 @@ export class TabDocView extends React.Component { pinDoc.presMovement = PresMovement.None; } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - PresBox.Instance?._selectedArray.clear(); - pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array + PresBox.Instance?.clearSelectedArray(); + pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); //Update selected array }); if ( CollectionDockingView.Instance && diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index f587dbbf6..ce87e6f89 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -1,4 +1,4 @@ -@import "../global/globalCssVariables"; +@import '../global/globalCssVariables'; .treeView-label { max-height: 1.5em; @@ -21,7 +21,7 @@ } .treeView-bulletIcons { - // width: $TREE_BULLET_WIDTH; + // width: $TREE_BULLET_WIDTH; width: 100%; height: 100%; @@ -101,6 +101,9 @@ .treeView-border { display: flex; overflow: hidden; + > ul { + width: 100%; + } } .treeView-border { @@ -118,7 +121,6 @@ } .formattedTextBox-cont { - .formattedTextbox-sidebar, .formattedTextbox-sidebar-inking { overflow: visible !important; @@ -144,12 +146,12 @@ pointer-events: all; cursor: pointer; - >svg { + > svg { margin-left: 0.25rem; margin-right: 0.25rem; } - >svg { + > svg { //display: none; opacity: 0; pointer-events: none; @@ -176,8 +178,7 @@ } .treeView-rightButtons { - - >svg, + > svg, .styleProvider-treeView-icon { display: inherit; opacity: unset; @@ -196,4 +197,4 @@ .treeView-header-inside { border: black 1px solid; -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 45a5e30ff..0fd326091 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -251,6 +251,7 @@ export class CollectionFreeFormView extends CollectionSubView() { delay: presDoc.presTransition, // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc, }; + //prettier-ignore switch (presDoc.presEffect) { - case PresEffect.Zoom: - return {renderDoc}; - case PresEffect.Fade: - return {renderDoc}; - case PresEffect.Flip: - return {renderDoc}; - case PresEffect.Rotate: - return {renderDoc}; - case PresEffect.Bounce: - return {renderDoc}; - case PresEffect.Roll: - return {renderDoc}; - case PresEffect.Lightspeed: - return {renderDoc}; - case PresEffect.None: - default: - return renderDoc; + default: + case PresEffect.None: return renderDoc; + case PresEffect.Zoom: return {renderDoc}; + case PresEffect.Fade: return {renderDoc}; + case PresEffect.Flip: return {renderDoc}; + case PresEffect.Rotate: return {renderDoc}; + case PresEffect.Bounce: return {renderDoc}; + case PresEffect.Roll: return {renderDoc}; + case PresEffect.Lightspeed: return {renderDoc}; } } public static EffectsProvider(layoutDoc: Doc, renderDoc: any) { return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex]) : renderDoc; } + private _disposers: { [name: string]: IReactionDisposer } = {}; + + constructor(props: any) { + super(props); + if ((Doc.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this)); + this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox + } + @observable public static Instance: PresBox; @observable _isChildActive = false; @@ -98,14 +99,11 @@ export class PresBox extends ViewBoxBaseComponent() { @observable _presTimer!: NodeJS.Timeout; @observable _presKeyEventsActive: boolean = false; - @observable _selectedArray: ObservableMap = new ObservableMap(); @observable _eleArray: HTMLElement[] = []; @observable _dragArray: HTMLElement[] = []; @observable _pathBoolean: boolean = false; @observable _expandBoolean: boolean = false; - private _disposers: { [name: string]: IReactionDisposer } = {}; - @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView @observable private transitionTools: boolean = false; @observable private newDocumentTools: boolean = false; @@ -152,11 +150,6 @@ export class PresBox extends ViewBoxBaseComponent() { if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; else return false; } - constructor(props: any) { - super(props); - if ((Doc.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this)); - this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox - } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); @@ -173,6 +166,10 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } + _selectedArray = new ObservableSet(); + clearSelectedArray = () => this._selectedArray.clear(); + addToSelectedArray = (doc: Doc) => this._selectedArray.add(doc); + removeFromSelectedArray = (doc: Doc) => this._selectedArray.delete(doc); _unmounting = false; @action @@ -188,6 +185,7 @@ export class PresBox extends ViewBoxBaseComponent() { @action componentDidMount() { + this.props.setContentView?.(this); this._unmounting = false; this.rootDoc._forceRenderEngine = 'timeline'; this.layoutDoc.presStatus = PresStatus.Edit; @@ -344,8 +342,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (targetDoc?.lastFrame !== undefined) { targetDoc._currentFrame = 0; } - if (!group) this._selectedArray.clear(); - this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array + if (!group) this.clearSelectedArray(); + this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list this.onHideDocument(); //Handles hide after/before } @@ -415,7 +413,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.layoutDoc.presCollection = srcContext; } const presStatus = this.rootDoc.presStatus; - const selViewCache = Array.from(this._selectedArray.keys()); + const selViewCache = Array.from(this._selectedArray); const dragViewCache = Array.from(this._dragArray); const eleViewCache = Array.from(this._eleArray); const self = this; @@ -423,8 +421,8 @@ export class PresBox extends ViewBoxBaseComponent() { const presDocView = DocumentManager.Instance.getDocumentView(self.rootDoc); if (presDocView) SelectionManager.SelectView(presDocView, false); self.rootDoc.presStatus = presStatus; - self._selectedArray.clear(); - selViewCache.forEach(doc => self._selectedArray.set(doc, undefined)); + self.clearSelectedArray(); + selViewCache.forEach(doc => self.addToSelectedArray(doc)); self._dragArray.splice(0, self._dragArray.length, ...dragViewCache); self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); }); @@ -792,7 +790,7 @@ export class PresBox extends ViewBoxBaseComponent() { * Method to get the list of selected items in the order in which they have been selected */ @computed get listOfSelected() { - const list = Array.from(this._selectedArray.keys()).map((doc: Doc, index: any) => { + return Array.from(this._selectedArray).map((doc: Doc, index: any) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (curDoc && curDoc === this.activeItem) @@ -816,7 +814,6 @@ export class PresBox extends ViewBoxBaseComponent() {
); }); - return list; } @action @@ -838,11 +835,11 @@ export class PresBox extends ViewBoxBaseComponent() { @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { if (!this._selectedArray.has(doc)) { - this._selectedArray.set(doc, undefined); + this.addToSelectedArray(doc); this._eleArray.push(ref); this._dragArray.push(drag); } else { - this._selectedArray.delete(doc); + this.removeFromSelectedArray(doc); this.removeFromArray(this._eleArray, doc); this.removeFromArray(this._dragArray, doc); } @@ -858,11 +855,11 @@ export class PresBox extends ViewBoxBaseComponent() { //Shift click @action shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { - this._selectedArray.clear(); + this.clearSelectedArray(); // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); if (this.activeItem) { for (let i = Math.min(this.itemIndex, this.childDocs.indexOf(doc)); i <= Math.max(this.itemIndex, this.childDocs.indexOf(doc)); i++) { - this._selectedArray.set(this.childDocs[i], undefined); + this.addToSelectedArray(this.childDocs[i]); this._eleArray.push(ref); this._dragArray.push(drag); } @@ -873,8 +870,8 @@ export class PresBox extends ViewBoxBaseComponent() { //regular click @action regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, selectPres = true) => { - this._selectedArray.clear(); - this._selectedArray.set(doc, undefined); + this.clearSelectedArray(); + this.addToSelectedArray(doc); this._eleArray.splice(0, this._eleArray.length, ref); this._dragArray.splice(0, this._dragArray.length, drag); focus && this.selectElement(doc); @@ -903,10 +900,10 @@ export class PresBox extends ViewBoxBaseComponent() { if (this.layoutDoc.presStatus === 'edit') { undoBatch( action(() => { - for (const doc of Array.from(this._selectedArray.keys())) { + for (const doc of this._selectedArray) { this.removeDocument(doc); } - this._selectedArray.clear(); + this.clearSelectedArray(); this._eleArray.length = 0; this._dragArray.length = 0; }) @@ -918,7 +915,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.updateMinimize(); } else if (this.layoutDoc.presStatus === 'edit') { - this._selectedArray.clear(); + this.clearSelectedArray(); this._eleArray.length = this._dragArray.length = 0; } else this.layoutDoc.presStatus = 'edit'; if (this._presTimer) clearTimeout(this._presTimer); @@ -931,7 +928,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) { // TODO: update to work properly this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1; - this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); + this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]); } else { this.next(); if (this._presTimer) { @@ -948,7 +945,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (e.shiftKey && this.itemIndex !== 0) { // TODO: update to work properly this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1; - this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); + this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]); } else { this.back(); if (this._presTimer) { @@ -966,8 +963,8 @@ export class PresBox extends ViewBoxBaseComponent() { break; case 'a': if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === 'edit') { - this._selectedArray.clear(); - this.childDocs.forEach(doc => this._selectedArray.set(doc, undefined)); + this.clearSelectedArray(); + this.childDocs.forEach(doc => this.addToSelectedArray(doc)); handled = true; } default: @@ -1115,7 +1112,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presTransition = timeInMS)); + this._selectedArray.forEach(doc => (doc.presTransition = timeInMS)); }; // Converts seconds to ms and updates presTransition @@ -1124,7 +1121,7 @@ export class PresBox extends ViewBoxBaseComponent() { 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)); + this._selectedArray.forEach(doc => (doc.presZoom = scale)); }; // Converts seconds to ms and updates presDuration @@ -1133,7 +1130,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presDuration = timeInMS)); + this._selectedArray.forEach(doc => (doc.presDuration = timeInMS)); }; /** @@ -1141,8 +1138,7 @@ export class PresBox extends ViewBoxBaseComponent() { */ @undoBatch updateMovement = action((movement: any, all?: boolean) => { - const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach(doc => { + (all ? this.childDocs : this._selectedArray).forEach(doc => { switch (movement) { case PresMovement.Zoom: //Pan and zoom doc.presMovement = PresMovement.Zoom; @@ -1166,21 +1162,21 @@ export class PresBox extends ViewBoxBaseComponent() { @action updateHideBefore = (activeItem: Doc) => { activeItem.presHideBefore = !activeItem.presHideBefore; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); + this._selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); }; @undoBatch @action updateHideAfter = (activeItem: Doc) => { activeItem.presHideAfter = !activeItem.presHideAfter; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); + this._selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); }; @undoBatch @action updateOpenDoc = (activeItem: Doc) => { activeItem.openDocument = !activeItem.openDocument; - Array.from(this._selectedArray.keys()).forEach(doc => { + this._selectedArray.forEach(doc => { doc.openDocument = activeItem.openDocument; }); }; @@ -1188,8 +1184,7 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch @action updateEffectDirection = (effect: any, all?: boolean) => { - const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach(doc => { + (all ? this.childDocs : this._selectedArray).forEach(doc => { const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null); switch (effect) { case PresEffect.Left: @@ -1215,29 +1210,17 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch @action updateEffect = (effect: any, all?: boolean) => { - const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach(doc => { + (all ? this.childDocs : this._selectedArray).forEach(doc => { const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null); + //prettier-ignore switch (effect) { - case PresEffect.Bounce: - tagDoc.presEffect = PresEffect.Bounce; - break; - case PresEffect.Fade: - tagDoc.presEffect = PresEffect.Fade; - break; - case PresEffect.Flip: - tagDoc.presEffect = PresEffect.Flip; - break; - case PresEffect.Roll: - tagDoc.presEffect = PresEffect.Roll; - break; - case PresEffect.Rotate: - tagDoc.presEffect = PresEffect.Rotate; - break; - case PresEffect.None: default: - tagDoc.presEffect = PresEffect.None; - break; + case PresEffect.None: tagDoc.presEffect = PresEffect.None; break; + case PresEffect.Bounce: tagDoc.presEffect = PresEffect.Bounce; break; + case PresEffect.Fade: tagDoc.presEffect = PresEffect.Fade; break; + case PresEffect.Flip: tagDoc.presEffect = PresEffect.Flip; break; + case PresEffect.Roll: tagDoc.presEffect = PresEffect.Roll; break; + case PresEffect.Rotate: tagDoc.presEffect = PresEffect.Rotate; break; } }); }; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 0cf15d297..3af8cad9a 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -47,6 +47,13 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get presStatus() { return this.presBox.presStatus; } + @computed get selectedArray() { + return this.presBoxView?._selectedArray; + } + @computed get presBoxView() { + const vpath = this.props.docViewPath(); + return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined; + } @computed get presBox() { return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!; } @@ -129,10 +136,9 @@ export class PresElementBox extends ViewBoxBaseComponent() {
{ - console.log('Clicked on slide with index: ', ind); e.stopPropagation(); e.preventDefault(); - PresBox.Instance.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); + this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); this.presExpandDocumentClick(); }}>
{`${ind + 1}.`}
@@ -181,15 +187,15 @@ export class PresElementBox extends ViewBoxBaseComponent() { e.stopPropagation(); e.preventDefault(); if (element && !(e.ctrlKey || e.metaKey)) { - if (PresBox.Instance._selectedArray.has(this.rootDoc)) { - PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false); + if (this.selectedArray?.has(this.rootDoc)) { + this.selectedArray.size === 1 && this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false); setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); } else { setupMoveUpEvents( this, e, (e: PointerEvent) => { - PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false); + this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false); return this.startDrag(e); }, emptyFunction, @@ -205,8 +211,8 @@ export class PresElementBox extends ViewBoxBaseComponent() { startDrag = (e: PointerEvent) => { const miniView: boolean = this.toolbarWidth <= 100; const activeItem = this.rootDoc; - const dragArray = PresBox.Instance._dragArray; - const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray()); + const dragArray = this.presBoxView?._dragArray ?? []; + const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []); if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc); dragData.dropAction = 'move'; dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc; @@ -221,7 +227,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { } else if (dragArray.length >= 1) { const doc = document.createElement('div'); doc.className = 'presItem-multiDrag'; - doc.innerText = 'Move ' + PresBox.Instance._selectedArray.size + ' slides'; + doc.innerText = 'Move ' + this.selectedArray?.size + ' slides'; doc.style.position = 'absolute'; doc.style.top = e.clientY + 'px'; doc.style.left = e.clientX - 50 + 'px'; @@ -286,10 +292,11 @@ export class PresElementBox extends ViewBoxBaseComponent() { @undoBatch removeItem = action((e: React.MouseEvent) => { e.stopPropagation(); - this.props.removeDocument?.(this.rootDoc); - if (PresBox.Instance._selectedArray.has(this.rootDoc)) { - PresBox.Instance._selectedArray.delete(this.rootDoc); + if (this.indexInPres < (this.presBoxView?.itemIndex || 0)) { + this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1; } + this.props.removeDocument?.(this.rootDoc); + this.presBoxView?.removeFromSelectedArray(this.rootDoc); this.removeAllRecordingInOverlay(); }); @@ -441,7 +448,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { } @computed get mainItem() { - const isSelected: boolean = PresBox.Instance?._selectedArray.has(this.rootDoc); + const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false; const toolbarWidth: number = this.toolbarWidth; const showMore: boolean = this.toolbarWidth >= 300; const miniView: boolean = this.toolbarWidth <= 110; @@ -463,12 +470,12 @@ export class PresElementBox extends ViewBoxBaseComponent() { onClick={e => { e.stopPropagation(); e.preventDefault(); - PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); + this.presBoxView?.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); this.showRecording(activeItem); }} onDoubleClick={action(e => { this.toggleProperties(); - PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true); + this.presBoxView?.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true); })} onPointerOver={this.onPointerOver} onPointerLeave={this.onPointerLeave} @@ -542,21 +549,29 @@ export class PresElementBox extends ViewBoxBaseComponent() { )} - {/* {this.indexInPres === 0 ? (null) :
{activeItem.groupWithUp ? "Ungroup" : "Group with up"}
}> -
activeItem.groupWithUp = !activeItem.groupWithUp} - style={{ - zIndex: 1000 - this.indexInPres, - fontWeight: 700, - backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined, - height: activeItem.groupWithUp ? 53 : 18, - transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined - }}> -
- e.stopPropagation()} /> -
-
-
} */} + {this.indexInPres === 0 ? null : ( + +
{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}
+ + }> +
(activeItem.groupWithUp = !activeItem.groupWithUp)} + style={{ + zIndex: 1000 - this.indexInPres, + fontWeight: 700, + backgroundColor: activeItem.groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined, + height: activeItem.groupWithUp ? 53 : 18, + transform: activeItem.groupWithUp ? 'translate(0, -17px)' : undefined, + }}> +
+ e.stopPropagation()} /> +
+
+
+ )} -- cgit v1.2.3-70-g09d2 From 94c38310c6b54d93e907007f20ba032d12697ca0 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Thu, 18 Aug 2022 12:33:34 -0400 Subject: fixed sizing bug --- src/client/documents/Documents.ts | 14 +++++-------- src/client/views/collections/CollectionSubView.tsx | 2 ++ src/client/views/nodes/LoadingBox.tsx | 24 ++++++++++++++++++---- 3 files changed, 27 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e54fe16de..f3ab7c788 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -827,9 +827,12 @@ export namespace Docs { viewFirstProps.author = Doc.CurrentUserEmail; let viewDoc: Doc; if (placeholderDoc) { + placeholderDoc._height = Number(options._height); + placeholderDoc._width = Number(options._width); viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); + } else { + viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); } - viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); Doc.assign(viewDoc, viewProps, true, true); ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); @@ -901,7 +904,7 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - export const filesToDocs = new Map(); + export const filesToDocs = new Map(); export function LoadingDocument(file: File | string, options: DocumentOptions, ytString?: string) { const fileName = typeof file == 'string' ? file : file.name; @@ -1854,13 +1857,6 @@ export namespace DocUtils { }); } - export function generatePlaceHolder(file: File, options: DocumentOptions) { - return Docs.Create.LoadingDocument(file, options); - // placeholder.file = file - // TODO: nda - modify loading doc so it only takes in options - // Docs.Create.LoadingDocument(options, ) - } - // copies the specified drag factory document export function copyDragFactory(dragFactory: Doc) { if (!dragFactory) return undefined; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 30467efa0..fd2c722d2 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -470,6 +470,7 @@ export function CollectionSubView(moreProps?: X) { // generatedDocuments.push(Docs.Create.LoadingDocument(files, options)); const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); + Docs.Create.filesToDocs.set(loading, files); DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { // uploadFilesToDocs and similar should return a placeholder, one for each placeholder @@ -477,6 +478,7 @@ export function CollectionSubView(moreProps?: X) { ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); // now that there is a doc do whatever slowload was going to do with that file + Docs.Create.filesToDocs.set(loading, file); DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 96620aff9..249461b67 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -5,6 +5,8 @@ import * as React from 'react'; import './LoadingBox.scss'; import ReactLoading from 'react-loading'; import { StrCast } from '../../../fields/Types'; +import { computed, observable } from 'mobx'; +import { Docs } from '../../documents/Documents'; @observer export class LoadingBox extends ViewBoxAnnotatableComponent() { @@ -12,6 +14,12 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { return FieldView.LayoutString(LoadingBox, fieldKey); } + @computed + private get isLoading() { + const file = Docs.Create.filesToDocs.get(this.rootDoc); + return file ? true : false; + } + componentDidMount() { console.log(this.rootDoc); // const file = Docs.Create.filesToDocs.get(this.rootDoc); @@ -44,10 +52,18 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { render() { return (
-

Loading:

-

-

{StrCast(this.rootDoc.title)}

- + {this.isLoading ? ( +
+

Loading:

+

+ {StrCast(this.rootDoc.title)} + +
+ ) : ( +
+ Error Loading File: {StrCast(this.rootDoc.title)} +
+ )}
); } -- cgit v1.2.3-70-g09d2 From 53dc1ae6077774a7235f2fe7f56ffa03f8a9aa5a Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 19 Aug 2022 09:21:28 -0400 Subject: fixed undo bug in dragManager where batches weren't being closed. fixed schemaheader copy method to copy all parameters. fixed notetaking columnresizer to create an UndoBatch. fixed notetakingview's columnHeaders to return the actual headers list, not a copy. fixed document decorations to not modify docsBeingDragged --- src/client/util/DragManager.ts | 2 +- src/client/views/DocumentDecorations.tsx | 4 +- .../views/collections/CollectionNoteTakingView.tsx | 20 ++--- .../CollectionNoteTakingViewDivider.tsx | 37 +++++---- .../views/collections/CollectionStackingView.tsx | 2 - src/client/views/nodes/trails/PresElementBox.tsx | 5 +- src/fields/SchemaHeaderField.ts | 92 +++++++++++----------- 7 files changed, 75 insertions(+), 87 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 947882958..eef5b9ce1 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -457,7 +457,7 @@ export namespace DragManager { document.removeEventListener('pointerup', upHandler, true); SnappingManager.SetIsDragging(false); SnappingManager.clearSnapLines(); - if (undo && batch.end()) UndoManager.Undo(); + if (batch.end() && undo) UndoManager.Undo(); docsBeingDragged.length = 0; }); var startWindowDragTimer: any; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 0db9eab69..a8dacff51 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -351,8 +351,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @action onPointerDown = (e: React.PointerEvent): void => { - DragManager.docsBeingDragged.push(...SelectionManager.Views().map(dv => dv.rootDoc)); - this._inkDragDocs = DragManager.docsBeingDragged + const views = SelectionManager.Views().map(dv => dv.rootDoc); + this._inkDragDocs = views .filter(doc => doc.type === DocumentType.INK) .map(doc => { if (InkStrokeProperties.Instance._lock) { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 447e1f0c8..0c5f69db0 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -53,7 +53,7 @@ export class CollectionNoteTakingView extends CollectionSubView !d[this.notetakingCategoryField] && !columnHeaders.find(sh => sh.heading === 'unset')); // @#Oberable draggedColIndex = ... @@ -142,17 +142,11 @@ export class CollectionNoteTakingView extends CollectionSubView !DragManager.docsBeingDragged.includes(d)); const sections = new Map(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); const rowCol = this.docsDraggedRowCol; - // filter out the currently dragged docs from the child docs, since we will insert them later - if (rowCol.length && DragManager.docsBeingDragged.length) { - const docIdsToRemove = new Set(); - DragManager.docsBeingDragged.forEach(d => docIdsToRemove.add(d[Id])); - docs = docs.filter(d => !docIdsToRemove.has(d[Id])); - } - // this will sort the docs into the correct columns (minus the ones you're currently dragging) docs.map(d => { const sectionValue = (d[this.notetakingCategoryField] as object) ?? `unset`; @@ -165,11 +159,10 @@ export class CollectionNoteTakingView extends CollectionSubView { + setColumnStartXCoords = (movementXScreen: number, colIndex: number) => { + const movementX = this.props.ScreenToLocalTransform().transformDirection(movementXScreen, 0)[0]; const leftHeader = this.columnHeaders[colIndex]; const rightHeader = this.columnHeaders[colIndex + 1]; leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth); diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx index 7d31b3193..2633bffeb 100644 --- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx @@ -1,5 +1,8 @@ import { action, observable } from 'mobx'; import * as React from 'react'; +import { emptyFunction, setupMoveUpEvents } from '../../../Utils'; +import { Transform } from '../../util/Transform'; +import { UndoManager } from '../../util/UndoManager'; interface DividerProps { index: number; @@ -13,28 +16,24 @@ export class CollectionNoteTakingViewDivider extends React.Component) => { - e.stopPropagation(); - e.preventDefault(); - window.removeEventListener('pointermove', this.onPointerMove); - window.removeEventListener('pointerup', this.onPointerUp); - window.addEventListener('pointermove', this.onPointerMove); - window.addEventListener('pointerup', this.onPointerUp); + const batch = UndoManager.StartBatch('resizing'); + setupMoveUpEvents( + this, + e, + (e, down, delta) => { + this.props.setColumnStartXCoords(delta[0], this.props.index); + return false; + }, + action(() => { + this.isResizingActive = false; + this.isHoverActive = false; + batch.end(); + }), + emptyFunction + ); this.isResizingActive = true; }; - @action - private onPointerUp = () => { - this.isResizingActive = false; - this.isHoverActive = false; - window.removeEventListener('pointermove', this.onPointerMove); - window.removeEventListener('pointerup', this.onPointerUp); - }; - - @action - onPointerMove = ({ movementX }: PointerEvent) => { - this.props.setColumnStartXCoords(movementX, this.props.index); - }; - render() { return (
docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); docs.splice(insertInd - offset, 0, ...newDocs); } - // reset drag manager docs, because we just dropped - DragManager.docsBeingDragged.length = 0; } } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' }); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 0cf15d297..c578f8bcf 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -250,10 +250,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { onPointerMove = (e: PointerEvent) => { const slide = this._itemRef.current!; - let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false; - for (const doc of DragManager.docsBeingDragged) { - if (!doc.presentationTargetDoc) dragIsPresItem = false; - } + const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentationTargetDoc); if (slide && dragIsPresItem) { const rect = slide.getBoundingClientRect(); const y = e.clientY - rect.top; //y position within the element. diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts index 3b02d0cfe..1321bc327 100644 --- a/src/fields/SchemaHeaderField.ts +++ b/src/fields/SchemaHeaderField.ts @@ -1,60 +1,60 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, primitive } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString, OnUpdate } from "./FieldSymbols"; -import { scriptingGlobal } from "../client/util/ScriptingGlobals"; -import { ColumnType } from "../client/views/collections/collectionSchema/CollectionSchemaView"; +import { Deserializable } from '../client/util/SerializationHelper'; +import { serializable, primitive } from 'serializr'; +import { ObjectField } from './ObjectField'; +import { Copy, ToScriptString, ToString, OnUpdate } from './FieldSymbols'; +import { scriptingGlobal } from '../client/util/ScriptingGlobals'; +import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView'; export const PastelSchemaPalette = new Map([ // ["pink1", "#FFB4E8"], - ["pink2", "#ff9cee"], - ["pink3", "#ffccf9"], - ["pink4", "#fcc2ff"], - ["pink5", "#f6a6ff"], - ["purple1", "#b28dff"], - ["purple2", "#c5a3ff"], - ["purple3", "#d5aaff"], - ["purple4", "#ecd4ff"], + ['pink2', '#ff9cee'], + ['pink3', '#ffccf9'], + ['pink4', '#fcc2ff'], + ['pink5', '#f6a6ff'], + ['purple1', '#b28dff'], + ['purple2', '#c5a3ff'], + ['purple3', '#d5aaff'], + ['purple4', '#ecd4ff'], // ["purple5", "#fb34ff"], - ["purple6", "#dcd3ff"], - ["purple7", "#a79aff"], - ["purple8", "#b5b9ff"], - ["purple9", "#97a2ff"], - ["bluegreen1", "#afcbff"], - ["bluegreen2", "#aff8db"], - ["bluegreen3", "#c4faf8"], - ["bluegreen4", "#85e3ff"], - ["bluegreen5", "#ace7ff"], + ['purple6', '#dcd3ff'], + ['purple7', '#a79aff'], + ['purple8', '#b5b9ff'], + ['purple9', '#97a2ff'], + ['bluegreen1', '#afcbff'], + ['bluegreen2', '#aff8db'], + ['bluegreen3', '#c4faf8'], + ['bluegreen4', '#85e3ff'], + ['bluegreen5', '#ace7ff'], // ["bluegreen6", "#6eb5ff"], - ["bluegreen7", "#bffcc6"], - ["bluegreen8", "#dbffd6"], - ["yellow1", "#f3ffe3"], - ["yellow2", "#e7ffac"], - ["yellow3", "#ffffd1"], - ["yellow4", "#fff5ba"], + ['bluegreen7', '#bffcc6'], + ['bluegreen8', '#dbffd6'], + ['yellow1', '#f3ffe3'], + ['yellow2', '#e7ffac'], + ['yellow3', '#ffffd1'], + ['yellow4', '#fff5ba'], // ["red1", "#ffc9de"], - ["red2", "#ffabab"], - ["red3", "#ffbebc"], - ["red4", "#ffcbc1"], - ["orange1", "#ffd5b3"], - ["gray", "#f1efeb"] + ['red2', '#ffabab'], + ['red3', '#ffbebc'], + ['red4', '#ffcbc1'], + ['orange1', '#ffd5b3'], + ['gray', '#f1efeb'], ]); export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)]; export const DarkPastelSchemaPalette = new Map([ - ["pink2", "#c932b0"], - ["purple4", "#913ad6"], - ["bluegreen1", "#3978ed"], - ["bluegreen7", "#2adb3e"], - ["bluegreen5", "#21b0eb"], - ["yellow4", "#edcc0c"], - ["red2", "#eb3636"], - ["orange1", "#f2740f"], + ['pink2', '#c932b0'], + ['purple4', '#913ad6'], + ['bluegreen1', '#3978ed'], + ['bluegreen7', '#2adb3e'], + ['bluegreen5', '#21b0eb'], + ['yellow4', '#edcc0c'], + ['red2', '#eb3636'], + ['orange1', '#f2740f'], ]); @scriptingGlobal -@Deserializable("schemaheader") +@Deserializable('schemaheader') export class SchemaHeaderField extends ObjectField { @serializable(primitive()) heading: string; @@ -69,7 +69,7 @@ export class SchemaHeaderField extends ObjectField { @serializable(primitive()) desc: boolean | undefined; // boolean determines sort order, undefined when no sort - constructor(heading: string = "", color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) { + constructor(heading: string = '', color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean, collapsed?: boolean) { super(); this.heading = heading; @@ -111,7 +111,7 @@ export class SchemaHeaderField extends ObjectField { } [Copy]() { - return new SchemaHeaderField(this.heading, this.color, this.type); + return new SchemaHeaderField(this.heading, this.color, this.type, this.width, this.desc, this.collapsed); } [ToScriptString]() { @@ -120,4 +120,4 @@ export class SchemaHeaderField extends ObjectField { [ToString]() { return `SchemaHeaderField`; } -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From b7c4d65a3bf04ff7d2c10d93be282a1b0a4650b3 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Fri, 19 Aug 2022 12:32:23 -0400 Subject: added comments, cleaned up code, could not get spinner to center --- src/client/documents/Documents.ts | 8 ++- src/client/views/collections/CollectionSubView.tsx | 4 +- src/client/views/nodes/LoadingBox.scss | 21 +++++++ src/client/views/nodes/LoadingBox.tsx | 64 ++++++++++------------ 4 files changed, 58 insertions(+), 39 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f3ab7c788..9e140ccd1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -815,6 +815,7 @@ export namespace Docs { dataProps[fieldKey + '-annotations'] = new List(); dataProps[fieldKey + '-sidebar'] = new List(); + // users placeholderDoc as proto if it exists const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); if (placeholderDoc) { @@ -826,6 +827,7 @@ export namespace Docs { viewFirstProps['acl-Override'] = 'None'; viewFirstProps.author = Doc.CurrentUserEmail; let viewDoc: Doc; + // determines whether viewDoc should be created using placeholder Doc or deafult if (placeholderDoc) { placeholderDoc._height = Number(options._height); placeholderDoc._width = Number(options._width); @@ -904,13 +906,13 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - export const filesToDocs = new Map(); + // Mapping of all loading docs to files, i.e. keeps track of files being uploaded in current session + export const docsToFiles = new Map(); export function LoadingDocument(file: File | string, options: DocumentOptions, ytString?: string) { const fileName = typeof file == 'string' ? file : file.name; options.title = fileName; - const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), fileName, { _height: 150, _width: 150, ...options }); - // filesToDocs.set(loading, file); + const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), fileName, { _height: 150, _width: 200, ...options }); return loading; } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index fd2c722d2..eef485b95 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -470,7 +470,7 @@ export function CollectionSubView(moreProps?: X) { // generatedDocuments.push(Docs.Create.LoadingDocument(files, options)); const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); - Docs.Create.filesToDocs.set(loading, files); + Docs.Create.docsToFiles.set(loading, files); DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { // uploadFilesToDocs and similar should return a placeholder, one for each placeholder @@ -478,7 +478,7 @@ export function CollectionSubView(moreProps?: X) { ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); // now that there is a doc do whatever slowload was going to do with that file - Docs.Create.filesToDocs.set(loading, file); + Docs.Create.docsToFiles.set(loading, file); DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss index e8890cd82..f6912f547 100644 --- a/src/client/views/nodes/LoadingBox.scss +++ b/src/client/views/nodes/LoadingBox.scss @@ -6,7 +6,28 @@ background-color: #fdfdfd; } +.textContainer { + margin: 5px; +} + +.textContainer { + justify-content: center; + align-content: center; +} + +.textContainer, .text { + overflow: hidden; text-overflow: ellipsis; + max-width: 80%; + text-align: center; +} + +.headerText { + text-align: center; + font-weight: bold; +} + +.spinner { text-align: center; } diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 249461b67..ab8878fc1 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -5,46 +5,42 @@ import * as React from 'react'; import './LoadingBox.scss'; import ReactLoading from 'react-loading'; import { StrCast } from '../../../fields/Types'; -import { computed, observable } from 'mobx'; +import { computed } from 'mobx'; import { Docs } from '../../documents/Documents'; +/** + * LoadingBox Class represents a placeholder doc for documents that are currently + * being uploaded to the server and being fetched by the client. The LoadingBox doc is then used to + * generate the actual type of doc that is required once the document has been successfully uploaded. + * + * Design considerations: + * We are using the docToFiles map in Documents to keep track of all files being uploaded in one session of the client. + * If the file is not found we assume an error has occurred with the file upload, e.g. it has been interrupted by a client refresh + * or network issues. The docToFiles essentially gets reset everytime the page is refreshed. + * + * TODOs: + * 1) ability to query server to retrieve files that already exist if users upload duplicate files. + * 2) ability to restart upload if there is an error + * 3) detect network error and notify the user + * 4 )if file upload gets interrupted, it still gets uploaded to the server if there are no network interruptions which leads to unused space. this could be + * handled with (1) + * + * @author naafiyan + */ @observer export class LoadingBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LoadingBox, fieldKey); } - @computed - private get isLoading() { - const file = Docs.Create.filesToDocs.get(this.rootDoc); + /** + * Returns true if file is uploading, false otherwise + */ + @computed private get isLoading(): boolean { + const file = Docs.Create.docsToFiles.get(this.rootDoc); return file ? true : false; } - componentDidMount() { - console.log(this.rootDoc); - // const file = Docs.Create.filesToDocs.get(this.rootDoc); - // if (file) { - // console.log('Got to file'); - // Docs.Create.filesToDocs.delete(this.rootDoc); - // // now that there is a doc do whatever slowload was going to do with that file - // if (typeof file === 'string') { - // // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder - // // (await DocUtils.uploadYoutubeVideo(files, options))); - // } else { - // // uploadFilesToDocs and similar should return a placeholder, one for each placeholder - // DocUtils.uploadFileToDoc(file, {}, this.rootDoc); - // } - // } else { - // // check if file now exists on server or not - // // if it does we need to retreieve it and create the appropriate doc (rest of what uploadFileToDoc was doing minus uploading) - // // if it doesn't display an error message "upload failed" - // } - // query endpoints to: - // check if file now exists on server or not - // if it does we need to retreieve it and create the appropriate doc (rest of what uploadFileToDoc was doing minus uploading) - // if it doesn't display an error message "upload failed" - } - constructor(props: any) { super(props); } @@ -53,15 +49,15 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { return (
{this.isLoading ? ( -
-

Loading:

-

+
+

Loading:

{StrCast(this.rootDoc.title)}
) : ( -
- Error Loading File: {StrCast(this.rootDoc.title)} +
+

Error Loading File:

+ {StrCast(this.rootDoc.title)}
)}
-- cgit v1.2.3-70-g09d2 From 85626694045f3863f6df9351156808017753de9e Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Fri, 19 Aug 2022 14:37:57 -0400 Subject: cleaned up comments --- src/client/views/nodes/LoadingBox.tsx | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index ab8878fc1..90bf90095 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -24,6 +24,8 @@ import { Docs } from '../../documents/Documents'; * 3) detect network error and notify the user * 4 )if file upload gets interrupted, it still gets uploaded to the server if there are no network interruptions which leads to unused space. this could be * handled with (1) + * 5) Fixing the stacking view bug + * 6) Fixing the CSS * * @author naafiyan */ -- cgit v1.2.3-70-g09d2 From 96a15ed27e9b17a77e6cab72d4087ddfe6066d2a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 22 Aug 2022 11:02:17 -0400 Subject: made ink strokes animatable. added currentFrame to playgroundFields --- package-lock.json | 866 +++++++++++++++---------------- src/client/util/InteractionUtils.tsx | 240 +++++---- src/client/views/InkStroke.scss | 25 +- src/client/views/InkStrokeProperties.ts | 12 +- src/client/views/InkingStroke.tsx | 6 +- src/client/views/MainView.tsx | 1 + src/client/views/nodes/DocumentView.scss | 1 + 7 files changed, 587 insertions(+), 564 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index f90b0b5f0..35b7683e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13288,12 +13288,12 @@ }, "abbrev": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "agent-base": { "version": "4.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "requires": { "es6-promisify": "^5.0.0" @@ -13301,7 +13301,7 @@ }, "agentkeepalive": { "version": "3.5.2", - "resolved": "", + "resolved": false, "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", "requires": { "humanize-ms": "^1.2.1" @@ -13309,7 +13309,7 @@ }, "ansi-align": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "requires": { "string-width": "^2.0.0" @@ -13317,12 +13317,12 @@ }, "ansi-regex": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" @@ -13330,27 +13330,27 @@ }, "ansicolors": { "version": "0.3.2", - "resolved": "", + "resolved": false, "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, "ansistyles": { "version": "0.1.3", - "resolved": "", + "resolved": false, "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=" }, "aproba": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "archy": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" }, "are-we-there-yet": { "version": "1.1.4", - "resolved": "", + "resolved": false, "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "requires": { "delegates": "^1.0.0", @@ -13359,7 +13359,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -13373,7 +13373,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -13383,12 +13383,12 @@ }, "asap": { "version": "2.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { "version": "0.2.4", - "resolved": "", + "resolved": false, "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { "safer-buffer": "~2.1.0" @@ -13396,32 +13396,32 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "asynckit": { "version": "0.4.0", - "resolved": "", + "resolved": false, "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sign2": { "version": "0.7.0", - "resolved": "", + "resolved": false, "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.8.0", - "resolved": "", + "resolved": false, "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "balanced-match": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "optional": true, "requires": { @@ -13430,7 +13430,7 @@ }, "bin-links": { "version": "1.1.8", - "resolved": "", + "resolved": false, "integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==", "requires": { "bluebird": "^3.5.3", @@ -13443,12 +13443,12 @@ }, "bluebird": { "version": "3.5.5", - "resolved": "", + "resolved": false, "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" }, "boxen": { "version": "1.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "requires": { "ansi-align": "^2.0.0", @@ -13462,7 +13462,7 @@ }, "brace-expansion": { "version": "1.1.11", - "resolved": "", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", @@ -13471,27 +13471,27 @@ }, "buffer-from": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" }, "builtins": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" }, "byline": { "version": "5.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" }, "byte-size": { "version": "5.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==" }, "cacache": { "version": "12.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "requires": { "bluebird": "^3.5.5", @@ -13513,27 +13513,27 @@ }, "call-limit": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==" }, "camelcase": { "version": "4.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, "capture-stack-trace": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=" }, "caseless": { "version": "0.12.0", - "resolved": "", + "resolved": false, "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { "version": "2.4.1", - "resolved": "", + "resolved": false, "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "requires": { "ansi-styles": "^3.2.1", @@ -13543,17 +13543,17 @@ }, "chownr": { "version": "1.1.4", - "resolved": "", + "resolved": false, "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "ci-info": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "cidr-regex": { "version": "2.0.10", - "resolved": "", + "resolved": false, "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", "requires": { "ip-regex": "^2.1.0" @@ -13561,12 +13561,12 @@ }, "cli-boxes": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, "cli-columns": { "version": "3.1.2", - "resolved": "", + "resolved": false, "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", "requires": { "string-width": "^2.0.0", @@ -13575,7 +13575,7 @@ }, "cli-table3": { "version": "0.5.1", - "resolved": "", + "resolved": false, "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "requires": { "colors": "^1.1.2", @@ -13585,7 +13585,7 @@ }, "cliui": { "version": "5.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "requires": { "string-width": "^3.1.0", @@ -13595,17 +13595,17 @@ "dependencies": { "ansi-regex": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", @@ -13615,7 +13615,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" @@ -13625,12 +13625,12 @@ }, "clone": { "version": "1.0.4", - "resolved": "", + "resolved": false, "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "cmd-shim": { "version": "3.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", "requires": { "graceful-fs": "^4.1.2", @@ -13639,12 +13639,12 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "color-convert": { "version": "1.9.1", - "resolved": "", + "resolved": false, "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "requires": { "color-name": "^1.1.1" @@ -13652,18 +13652,18 @@ }, "color-name": { "version": "1.1.3", - "resolved": "", + "resolved": false, "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.3.3", - "resolved": "", + "resolved": false, "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", "optional": true }, "columnify": { "version": "1.5.4", - "resolved": "", + "resolved": false, "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", "requires": { "strip-ansi": "^3.0.0", @@ -13672,7 +13672,7 @@ }, "combined-stream": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { "delayed-stream": "~1.0.0" @@ -13680,12 +13680,12 @@ }, "concat-map": { "version": "0.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", - "resolved": "", + "resolved": false, "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { "buffer-from": "^1.0.0", @@ -13696,7 +13696,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -13710,7 +13710,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -13720,7 +13720,7 @@ }, "config-chain": { "version": "1.1.12", - "resolved": "", + "resolved": false, "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "requires": { "ini": "^1.3.4", @@ -13729,7 +13729,7 @@ }, "configstore": { "version": "3.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==", "requires": { "dot-prop": "^4.2.1", @@ -13742,12 +13742,12 @@ }, "console-control-strings": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "copy-concurrently": { "version": "1.0.5", - "resolved": "", + "resolved": false, "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "requires": { "aproba": "^1.1.1", @@ -13760,24 +13760,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "iferr": { "version": "0.1.5", - "resolved": "", + "resolved": false, "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" } } }, "core-util-is": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-error-class": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "requires": { "capture-stack-trace": "^1.0.0" @@ -13785,7 +13785,7 @@ }, "cross-spawn": { "version": "5.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { "lru-cache": "^4.0.1", @@ -13795,7 +13795,7 @@ "dependencies": { "lru-cache": { "version": "4.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { "pseudomap": "^1.0.2", @@ -13804,24 +13804,24 @@ }, "yallist": { "version": "2.1.2", - "resolved": "", + "resolved": false, "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, "crypto-random-string": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, "cyclist": { "version": "0.2.2", - "resolved": "", + "resolved": false, "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" }, "dashdash": { "version": "1.14.1", - "resolved": "", + "resolved": false, "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" @@ -13829,7 +13829,7 @@ }, "debug": { "version": "3.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { "ms": "2.0.0" @@ -13837,34 +13837,34 @@ "dependencies": { "ms": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, "debuglog": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=" }, "decamelize": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", - "resolved": "", + "resolved": false, "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "deep-extend": { "version": "0.6.0", - "resolved": "", + "resolved": false, "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "defaults": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "requires": { "clone": "^1.0.2" @@ -13872,7 +13872,7 @@ }, "define-properties": { "version": "1.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "requires": { "object-keys": "^1.0.12" @@ -13880,27 +13880,27 @@ }, "delayed-stream": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "detect-indent": { "version": "5.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" }, "detect-newline": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" }, "dezalgo": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", "requires": { "asap": "^2.0.0", @@ -13909,7 +13909,7 @@ }, "dot-prop": { "version": "4.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", "requires": { "is-obj": "^1.0.0" @@ -13917,17 +13917,17 @@ }, "dotenv": { "version": "5.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==" }, "duplexer3": { "version": "0.1.4", - "resolved": "", + "resolved": false, "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "duplexify": { "version": "3.6.0", - "resolved": "", + "resolved": false, "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "requires": { "end-of-stream": "^1.0.0", @@ -13938,7 +13938,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -13952,7 +13952,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -13962,7 +13962,7 @@ }, "ecc-jsbn": { "version": "0.1.2", - "resolved": "", + "resolved": false, "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { @@ -13972,17 +13972,17 @@ }, "editor": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=" }, "emoji-regex": { "version": "7.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, "encoding": { "version": "0.1.12", - "resolved": "", + "resolved": false, "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { "iconv-lite": "~0.4.13" @@ -13990,7 +13990,7 @@ }, "end-of-stream": { "version": "1.4.1", - "resolved": "", + "resolved": false, "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { "once": "^1.4.0" @@ -13998,17 +13998,17 @@ }, "env-paths": { "version": "2.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" }, "err-code": { "version": "1.1.2", - "resolved": "", + "resolved": false, "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" }, "errno": { "version": "0.1.7", - "resolved": "", + "resolved": false, "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "requires": { "prr": "~1.0.1" @@ -14016,7 +14016,7 @@ }, "es-abstract": { "version": "1.12.0", - "resolved": "", + "resolved": false, "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "requires": { "es-to-primitive": "^1.1.1", @@ -14028,7 +14028,7 @@ }, "es-to-primitive": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "requires": { "is-callable": "^1.1.4", @@ -14038,12 +14038,12 @@ }, "es6-promise": { "version": "4.2.8", - "resolved": "", + "resolved": false, "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "es6-promisify": { "version": "5.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "requires": { "es6-promise": "^4.0.3" @@ -14051,12 +14051,12 @@ }, "escape-string-regexp": { "version": "1.0.5", - "resolved": "", + "resolved": false, "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "execa": { "version": "0.7.0", - "resolved": "", + "resolved": false, "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { "cross-spawn": "^5.0.1", @@ -14070,39 +14070,39 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" } } }, "extend": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extsprintf": { "version": "1.3.0", - "resolved": "", + "resolved": false, "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-json-stable-stringify": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "figgy-pudding": { "version": "3.5.1", - "resolved": "", + "resolved": false, "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" }, "find-npm-prefix": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==" }, "flush-write-stream": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "requires": { "inherits": "^2.0.1", @@ -14111,7 +14111,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -14125,7 +14125,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -14135,12 +14135,12 @@ }, "forever-agent": { "version": "0.6.1", - "resolved": "", + "resolved": false, "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.2", - "resolved": "", + "resolved": false, "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { "asynckit": "^0.4.0", @@ -14150,7 +14150,7 @@ }, "from2": { "version": "2.3.0", - "resolved": "", + "resolved": false, "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "requires": { "inherits": "^2.0.1", @@ -14159,7 +14159,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -14173,7 +14173,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -14183,7 +14183,7 @@ }, "fs-minipass": { "version": "1.2.7", - "resolved": "", + "resolved": false, "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "requires": { "minipass": "^2.6.0" @@ -14191,7 +14191,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": "", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", @@ -14202,7 +14202,7 @@ }, "fs-vacuum": { "version": "1.2.10", - "resolved": "", + "resolved": false, "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", "requires": { "graceful-fs": "^4.1.2", @@ -14212,7 +14212,7 @@ }, "fs-write-stream-atomic": { "version": "1.0.10", - "resolved": "", + "resolved": false, "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "requires": { "graceful-fs": "^4.1.2", @@ -14223,12 +14223,12 @@ "dependencies": { "iferr": { "version": "0.1.5", - "resolved": "", + "resolved": false, "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -14242,7 +14242,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -14252,17 +14252,17 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "gauge": { "version": "2.7.4", - "resolved": "", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "^1.0.3", @@ -14277,12 +14277,12 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "string-width": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", @@ -14294,12 +14294,12 @@ }, "genfun": { "version": "5.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==" }, "gentle-fs": { "version": "2.3.1", - "resolved": "", + "resolved": false, "integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==", "requires": { "aproba": "^1.1.2", @@ -14317,24 +14317,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "iferr": { "version": "0.1.5", - "resolved": "", + "resolved": false, "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" } } }, "get-caller-file": { "version": "2.0.5", - "resolved": "", + "resolved": false, "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-stream": { "version": "4.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "requires": { "pump": "^3.0.0" @@ -14342,7 +14342,7 @@ }, "getpass": { "version": "0.1.7", - "resolved": "", + "resolved": false, "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "^1.0.0" @@ -14350,7 +14350,7 @@ }, "glob": { "version": "7.1.6", - "resolved": "", + "resolved": false, "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", @@ -14363,7 +14363,7 @@ }, "global-dirs": { "version": "0.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "requires": { "ini": "^1.3.4" @@ -14371,7 +14371,7 @@ }, "got": { "version": "6.7.1", - "resolved": "", + "resolved": false, "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "requires": { "create-error-class": "^3.0.0", @@ -14389,24 +14389,24 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" } } }, "graceful-fs": { "version": "4.2.4", - "resolved": "", + "resolved": false, "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "har-schema": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { "ajv": "^6.12.3", @@ -14415,7 +14415,7 @@ "dependencies": { "ajv": { "version": "6.12.6", - "resolved": "", + "resolved": false, "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "^3.1.1", @@ -14426,19 +14426,19 @@ }, "fast-deep-equal": { "version": "3.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "json-schema-traverse": { "version": "0.4.1", - "resolved": "", + "resolved": false, "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" } } }, "has": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "requires": { "function-bind": "^1.1.1" @@ -14446,32 +14446,32 @@ }, "has-flag": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-unicode": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "hosted-git-info": { "version": "2.8.9", - "resolved": "", + "resolved": false, "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "http-cache-semantics": { "version": "3.8.1", - "resolved": "", + "resolved": false, "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" }, "http-proxy-agent": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "requires": { "agent-base": "4", @@ -14480,7 +14480,7 @@ }, "http-signature": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { "assert-plus": "^1.0.0", @@ -14490,7 +14490,7 @@ }, "https-proxy-agent": { "version": "2.2.4", - "resolved": "", + "resolved": false, "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "requires": { "agent-base": "^4.3.0", @@ -14499,7 +14499,7 @@ }, "humanize-ms": { "version": "1.2.1", - "resolved": "", + "resolved": false, "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", "requires": { "ms": "^2.0.0" @@ -14507,7 +14507,7 @@ }, "iconv-lite": { "version": "0.4.23", - "resolved": "", + "resolved": false, "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -14515,12 +14515,12 @@ }, "iferr": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==" }, "ignore-walk": { "version": "3.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "requires": { "minimatch": "^3.0.4" @@ -14528,22 +14528,22 @@ }, "import-lazy": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" }, "imurmurhash": { "version": "0.1.4", - "resolved": "", + "resolved": false, "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "infer-owner": { "version": "1.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "inflight": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", @@ -14552,17 +14552,17 @@ }, "inherits": { "version": "2.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", - "resolved": "", + "resolved": false, "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "init-package-json": { "version": "1.10.3", - "resolved": "", + "resolved": false, "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", "requires": { "glob": "^7.1.1", @@ -14577,22 +14577,22 @@ }, "ip": { "version": "1.1.5", - "resolved": "", + "resolved": false, "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, "ip-regex": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" }, "is-callable": { "version": "1.1.4", - "resolved": "", + "resolved": false, "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-ci": { "version": "1.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "requires": { "ci-info": "^1.5.0" @@ -14600,14 +14600,14 @@ "dependencies": { "ci-info": { "version": "1.6.0", - "resolved": "", + "resolved": false, "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" } } }, "is-cidr": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==", "requires": { "cidr-regex": "^2.0.10" @@ -14615,12 +14615,12 @@ }, "is-date-object": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" @@ -14628,7 +14628,7 @@ }, "is-installed-globally": { "version": "0.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "requires": { "global-dirs": "^0.1.0", @@ -14637,17 +14637,17 @@ }, "is-npm": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" }, "is-obj": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-path-inside": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "requires": { "path-is-inside": "^1.0.1" @@ -14655,12 +14655,12 @@ }, "is-redirect": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" }, "is-regex": { "version": "1.0.4", - "resolved": "", + "resolved": false, "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "requires": { "has": "^1.0.1" @@ -14668,17 +14668,17 @@ }, "is-retry-allowed": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" }, "is-stream": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "requires": { "has-symbols": "^1.0.0" @@ -14686,53 +14686,53 @@ }, "is-typedarray": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "isarray": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isstream": { "version": "0.1.2", - "resolved": "", + "resolved": false, "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jsbn": { "version": "0.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "optional": true }, "json-parse-better-errors": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema": { "version": "0.4.0", - "resolved": "", + "resolved": false, "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-stringify-safe": { "version": "5.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonparse": { "version": "1.3.1", - "resolved": "", + "resolved": false, "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, "jsprim": { "version": "1.4.2", - "resolved": "", + "resolved": false, "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "requires": { "assert-plus": "1.0.0", @@ -14743,7 +14743,7 @@ }, "latest-version": { "version": "3.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "requires": { "package-json": "^4.0.0" @@ -14751,12 +14751,12 @@ }, "lazy-property": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=" }, "libcipm": { "version": "4.0.8", - "resolved": "", + "resolved": false, "integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==", "requires": { "bin-links": "^1.1.2", @@ -14778,7 +14778,7 @@ }, "libnpm": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", "requires": { "bin-links": "^1.1.2", @@ -14805,7 +14805,7 @@ }, "libnpmaccess": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", "requires": { "aproba": "^2.0.0", @@ -14816,7 +14816,7 @@ }, "libnpmconfig": { "version": "1.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", "requires": { "figgy-pudding": "^3.5.1", @@ -14826,7 +14826,7 @@ "dependencies": { "find-up": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { "locate-path": "^3.0.0" @@ -14834,7 +14834,7 @@ }, "locate-path": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { "p-locate": "^3.0.0", @@ -14843,7 +14843,7 @@ }, "p-limit": { "version": "2.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "requires": { "p-try": "^2.0.0" @@ -14851,7 +14851,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { "p-limit": "^2.0.0" @@ -14859,14 +14859,14 @@ }, "p-try": { "version": "2.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" } } }, "libnpmhook": { "version": "5.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", "requires": { "aproba": "^2.0.0", @@ -14877,7 +14877,7 @@ }, "libnpmorg": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", "requires": { "aproba": "^2.0.0", @@ -14888,7 +14888,7 @@ }, "libnpmpublish": { "version": "1.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", "requires": { "aproba": "^2.0.0", @@ -14904,7 +14904,7 @@ }, "libnpmsearch": { "version": "2.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", "requires": { "figgy-pudding": "^3.5.1", @@ -14914,7 +14914,7 @@ }, "libnpmteam": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", "requires": { "aproba": "^2.0.0", @@ -14925,7 +14925,7 @@ }, "libnpx": { "version": "10.2.4", - "resolved": "", + "resolved": false, "integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==", "requires": { "dotenv": "^5.0.1", @@ -14940,7 +14940,7 @@ }, "lock-verify": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", "requires": { "npm-package-arg": "^6.1.0", @@ -14949,7 +14949,7 @@ }, "lockfile": { "version": "1.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", "requires": { "signal-exit": "^3.0.2" @@ -14957,12 +14957,12 @@ }, "lodash._baseindexof": { "version": "3.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=" }, "lodash._baseuniq": { "version": "4.6.0", - "resolved": "", + "resolved": false, "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", "requires": { "lodash._createset": "~4.0.0", @@ -14971,17 +14971,17 @@ }, "lodash._bindcallback": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" }, "lodash._cacheindexof": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=" }, "lodash._createcache": { "version": "3.1.2", - "resolved": "", + "resolved": false, "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", "requires": { "lodash._getnative": "^3.0.0" @@ -14989,52 +14989,52 @@ }, "lodash._createset": { "version": "4.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" }, "lodash._getnative": { "version": "3.9.1", - "resolved": "", + "resolved": false, "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" }, "lodash._root": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" }, "lodash.clonedeep": { "version": "4.5.0", - "resolved": "", + "resolved": false, "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, "lodash.restparam": { "version": "3.6.1", - "resolved": "", + "resolved": false, "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" }, "lodash.union": { "version": "4.6.0", - "resolved": "", + "resolved": false, "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "lodash.uniq": { "version": "4.5.0", - "resolved": "", + "resolved": false, "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, "lodash.without": { "version": "4.4.0", - "resolved": "", + "resolved": false, "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=" }, "lowercase-keys": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lru-cache": { "version": "5.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { "yallist": "^3.0.2" @@ -15042,7 +15042,7 @@ }, "make-dir": { "version": "1.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "requires": { "pify": "^3.0.0" @@ -15050,7 +15050,7 @@ }, "make-fetch-happen": { "version": "5.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", "requires": { "agentkeepalive": "^3.4.1", @@ -15068,17 +15068,17 @@ }, "meant": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg==" }, "mime-db": { "version": "1.35.0", - "resolved": "", + "resolved": false, "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" }, "mime-types": { "version": "2.1.19", - "resolved": "", + "resolved": false, "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "requires": { "mime-db": "~1.35.0" @@ -15086,7 +15086,7 @@ }, "minimatch": { "version": "3.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" @@ -15094,12 +15094,12 @@ }, "minimist": { "version": "1.2.6", - "resolved": "", + "resolved": false, "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minizlib": { "version": "1.3.3", - "resolved": "", + "resolved": false, "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "requires": { "minipass": "^2.9.0" @@ -15107,7 +15107,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": "", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", @@ -15118,7 +15118,7 @@ }, "mississippi": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "requires": { "concat-stream": "^1.5.0", @@ -15135,7 +15135,7 @@ }, "mkdirp": { "version": "0.5.5", - "resolved": "", + "resolved": false, "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "requires": { "minimist": "^1.2.5" @@ -15143,14 +15143,14 @@ "dependencies": { "minimist": { "version": "1.2.6", - "resolved": "", + "resolved": false, "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" } } }, "move-concurrently": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "requires": { "aproba": "^1.1.1", @@ -15163,24 +15163,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" } } }, "ms": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "mute-stream": { "version": "0.0.7", - "resolved": "", + "resolved": false, "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, "node-fetch-npm": { "version": "2.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", "requires": { "encoding": "^0.1.11", @@ -15190,7 +15190,7 @@ }, "node-gyp": { "version": "5.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==", "requires": { "env-paths": "^2.2.0", @@ -15208,7 +15208,7 @@ }, "nopt": { "version": "4.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", "requires": { "abbrev": "1", @@ -15217,7 +15217,7 @@ }, "normalize-package-data": { "version": "2.5.0", - "resolved": "", + "resolved": false, "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "requires": { "hosted-git-info": "^2.1.4", @@ -15228,7 +15228,7 @@ "dependencies": { "resolve": { "version": "1.10.0", - "resolved": "", + "resolved": false, "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "requires": { "path-parse": "^1.0.6" @@ -15238,7 +15238,7 @@ }, "npm-audit-report": { "version": "1.3.3", - "resolved": "", + "resolved": false, "integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==", "requires": { "cli-table3": "^0.5.0", @@ -15247,7 +15247,7 @@ }, "npm-bundled": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "requires": { "npm-normalize-package-bin": "^1.0.1" @@ -15255,12 +15255,12 @@ }, "npm-cache-filename": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=" }, "npm-install-checks": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", "requires": { "semver": "^2.3.0 || 3.x || 4 || 5" @@ -15268,7 +15268,7 @@ }, "npm-lifecycle": { "version": "3.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==", "requires": { "byline": "^5.0.0", @@ -15283,17 +15283,17 @@ }, "npm-logical-tree": { "version": "1.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==" }, "npm-normalize-package-bin": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" }, "npm-package-arg": { "version": "6.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", "requires": { "hosted-git-info": "^2.7.1", @@ -15304,7 +15304,7 @@ }, "npm-packlist": { "version": "1.4.8", - "resolved": "", + "resolved": false, "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "requires": { "ignore-walk": "^3.0.1", @@ -15314,7 +15314,7 @@ }, "npm-pick-manifest": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", "requires": { "figgy-pudding": "^3.5.1", @@ -15324,7 +15324,7 @@ }, "npm-profile": { "version": "4.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", "requires": { "aproba": "^1.1.2 || 2", @@ -15334,7 +15334,7 @@ }, "npm-registry-fetch": { "version": "4.0.7", - "resolved": "", + "resolved": false, "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==", "requires": { "JSONStream": "^1.3.4", @@ -15348,14 +15348,14 @@ "dependencies": { "safe-buffer": { "version": "5.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, "npm-run-path": { "version": "2.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { "path-key": "^2.0.0" @@ -15363,12 +15363,12 @@ }, "npm-user-validate": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==" }, "npmlog": { "version": "4.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { "are-we-there-yet": "~1.1.2", @@ -15379,27 +15379,27 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.9.0", - "resolved": "", + "resolved": false, "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-keys": { "version": "1.0.12", - "resolved": "", + "resolved": false, "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" }, "object.getownpropertydescriptors": { "version": "2.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "requires": { "define-properties": "^1.1.2", @@ -15408,7 +15408,7 @@ }, "once": { "version": "1.4.0", - "resolved": "", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" @@ -15416,22 +15416,22 @@ }, "opener": { "version": "1.5.2", - "resolved": "", + "resolved": false, "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" }, "os-homedir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "^1.0.0", @@ -15440,12 +15440,12 @@ }, "p-finally": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "package-json": { "version": "4.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { "got": "^6.7.1", @@ -15456,7 +15456,7 @@ }, "pacote": { "version": "9.5.12", - "resolved": "", + "resolved": false, "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", "requires": { "bluebird": "^3.5.3", @@ -15493,7 +15493,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": "", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", @@ -15504,7 +15504,7 @@ }, "parallel-transform": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "requires": { "cyclist": "~0.2.2", @@ -15514,7 +15514,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -15528,7 +15528,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -15538,57 +15538,57 @@ }, "path-exists": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, "path-key": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.7", - "resolved": "", + "resolved": false, "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "performance-now": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "prepend-http": { "version": "1.0.4", - "resolved": "", + "resolved": false, "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "process-nextick-args": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "promise-inflight": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "promise-retry": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", "requires": { "err-code": "^1.0.0", @@ -15597,14 +15597,14 @@ "dependencies": { "retry": { "version": "0.10.1", - "resolved": "", + "resolved": false, "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" } } }, "promzard": { "version": "0.3.0", - "resolved": "", + "resolved": false, "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", "requires": { "read": "1" @@ -15612,12 +15612,12 @@ }, "proto-list": { "version": "1.2.4", - "resolved": "", + "resolved": false, "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" }, "protoduck": { "version": "5.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", "requires": { "genfun": "^5.0.0" @@ -15625,22 +15625,22 @@ }, "prr": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, "pseudomap": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.29", - "resolved": "", + "resolved": false, "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" }, "pump": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", @@ -15649,7 +15649,7 @@ }, "pumpify": { "version": "1.5.1", - "resolved": "", + "resolved": false, "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "requires": { "duplexify": "^3.6.0", @@ -15659,7 +15659,7 @@ "dependencies": { "pump": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "requires": { "end-of-stream": "^1.1.0", @@ -15670,22 +15670,22 @@ }, "punycode": { "version": "1.4.1", - "resolved": "", + "resolved": false, "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qrcode-terminal": { "version": "0.12.0", - "resolved": "", + "resolved": false, "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==" }, "qs": { "version": "6.5.2", - "resolved": "", + "resolved": false, "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { "version": "6.8.2", - "resolved": "", + "resolved": false, "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", "requires": { "decode-uri-component": "^0.2.0", @@ -15695,12 +15695,12 @@ }, "qw": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=" }, "rc": { "version": "1.2.8", - "resolved": "", + "resolved": false, "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { "deep-extend": "^0.6.0", @@ -15711,7 +15711,7 @@ }, "read": { "version": "1.0.7", - "resolved": "", + "resolved": false, "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", "requires": { "mute-stream": "~0.0.4" @@ -15719,7 +15719,7 @@ }, "read-cmd-shim": { "version": "1.0.5", - "resolved": "", + "resolved": false, "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", "requires": { "graceful-fs": "^4.1.2" @@ -15727,7 +15727,7 @@ }, "read-installed": { "version": "4.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", "requires": { "debuglog": "^1.0.1", @@ -15741,7 +15741,7 @@ }, "read-package-json": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", "requires": { "glob": "^7.1.1", @@ -15753,7 +15753,7 @@ }, "read-package-tree": { "version": "5.3.1", - "resolved": "", + "resolved": false, "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", "requires": { "read-package-json": "^2.0.0", @@ -15763,7 +15763,7 @@ }, "readable-stream": { "version": "3.6.0", - "resolved": "", + "resolved": false, "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", @@ -15773,7 +15773,7 @@ }, "readdir-scoped-modules": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", "requires": { "debuglog": "^1.0.1", @@ -15784,7 +15784,7 @@ }, "registry-auth-token": { "version": "3.4.0", - "resolved": "", + "resolved": false, "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "requires": { "rc": "^1.1.6", @@ -15793,7 +15793,7 @@ }, "registry-url": { "version": "3.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { "rc": "^1.0.1" @@ -15801,7 +15801,7 @@ }, "request": { "version": "2.88.0", - "resolved": "", + "resolved": false, "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", @@ -15828,27 +15828,27 @@ }, "require-directory": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve-from": { "version": "4.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "retry": { "version": "0.12.0", - "resolved": "", + "resolved": false, "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, "rimraf": { "version": "2.7.1", - "resolved": "", + "resolved": false, "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "requires": { "glob": "^7.1.3" @@ -15856,7 +15856,7 @@ }, "run-queue": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "requires": { "aproba": "^1.1.1" @@ -15864,29 +15864,29 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" } } }, "safe-buffer": { "version": "5.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { "version": "5.7.1", - "resolved": "", + "resolved": false, "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-diff": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { "semver": "^5.0.3" @@ -15894,12 +15894,12 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "sha": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", "requires": { "graceful-fs": "^4.1.2" @@ -15907,7 +15907,7 @@ }, "shebang-command": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { "shebang-regex": "^1.0.0" @@ -15915,27 +15915,27 @@ }, "shebang-regex": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "signal-exit": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "slide": { "version": "1.1.6", - "resolved": "", + "resolved": false, "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" }, "smart-buffer": { "version": "4.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" }, "socks": { "version": "2.3.3", - "resolved": "", + "resolved": false, "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", "requires": { "ip": "1.1.5", @@ -15944,7 +15944,7 @@ }, "socks-proxy-agent": { "version": "4.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "requires": { "agent-base": "~4.2.1", @@ -15953,7 +15953,7 @@ "dependencies": { "agent-base": { "version": "4.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "requires": { "es6-promisify": "^5.0.0" @@ -15963,12 +15963,12 @@ }, "sorted-object": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=" }, "sorted-union-stream": { "version": "2.1.3", - "resolved": "", + "resolved": false, "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", "requires": { "from2": "^1.3.0", @@ -15977,7 +15977,7 @@ "dependencies": { "from2": { "version": "1.3.0", - "resolved": "", + "resolved": false, "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", "requires": { "inherits": "~2.0.1", @@ -15986,12 +15986,12 @@ }, "isarray": { "version": "0.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "readable-stream": { "version": "1.1.14", - "resolved": "", + "resolved": false, "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", @@ -16002,14 +16002,14 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "", + "resolved": false, "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, "spdx-correct": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "requires": { "spdx-expression-parse": "^3.0.0", @@ -16018,12 +16018,12 @@ }, "spdx-exceptions": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" }, "spdx-expression-parse": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "requires": { "spdx-exceptions": "^2.1.0", @@ -16032,17 +16032,17 @@ }, "spdx-license-ids": { "version": "3.0.5", - "resolved": "", + "resolved": false, "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" }, "split-on-first": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" }, "sshpk": { "version": "1.14.2", - "resolved": "", + "resolved": false, "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { "asn1": "~0.2.3", @@ -16058,7 +16058,7 @@ }, "ssri": { "version": "6.0.2", - "resolved": "", + "resolved": false, "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" @@ -16066,7 +16066,7 @@ }, "stream-each": { "version": "1.2.2", - "resolved": "", + "resolved": false, "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", "requires": { "end-of-stream": "^1.1.0", @@ -16075,7 +16075,7 @@ }, "stream-iterate": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", "requires": { "readable-stream": "^2.1.5", @@ -16084,7 +16084,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -16098,7 +16098,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -16108,17 +16108,17 @@ }, "stream-shift": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, "strict-uri-encode": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" }, "string-width": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -16127,17 +16127,17 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "strip-ansi": { "version": "4.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { "ansi-regex": "^3.0.0" @@ -16160,12 +16160,12 @@ }, "stringify-package": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==" }, "strip-ansi": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -16173,17 +16173,17 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-json-comments": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supports-color": { "version": "5.4.0", - "resolved": "", + "resolved": false, "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { "has-flag": "^3.0.0" @@ -16191,7 +16191,7 @@ }, "tar": { "version": "4.4.19", - "resolved": "", + "resolved": false, "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", "requires": { "chownr": "^1.1.4", @@ -16205,7 +16205,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": "", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", @@ -16214,19 +16214,19 @@ }, "safe-buffer": { "version": "5.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "yallist": { "version": "3.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, "term-size": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "requires": { "execa": "^0.7.0" @@ -16234,17 +16234,17 @@ }, "text-table": { "version": "0.2.0", - "resolved": "", + "resolved": false, "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, "through": { "version": "2.3.8", - "resolved": "", + "resolved": false, "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "requires": { "readable-stream": "^2.1.5", @@ -16253,7 +16253,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -16267,7 +16267,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -16277,17 +16277,17 @@ }, "timed-out": { "version": "4.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, "tiny-relative-date": { "version": "1.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==" }, "tough-cookie": { "version": "2.4.3", - "resolved": "", + "resolved": false, "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { "psl": "^1.1.24", @@ -16296,7 +16296,7 @@ }, "tunnel-agent": { "version": "0.6.0", - "resolved": "", + "resolved": false, "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { "safe-buffer": "^5.0.1" @@ -16304,28 +16304,28 @@ }, "tweetnacl": { "version": "0.14.5", - "resolved": "", + "resolved": false, "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, "typedarray": { "version": "0.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "uid-number": { "version": "0.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" }, "umask": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=" }, "unique-filename": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "requires": { "unique-slug": "^2.0.0" @@ -16333,7 +16333,7 @@ }, "unique-slug": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", "requires": { "imurmurhash": "^0.1.4" @@ -16341,7 +16341,7 @@ }, "unique-string": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "requires": { "crypto-random-string": "^1.0.0" @@ -16349,17 +16349,17 @@ }, "unpipe": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unzip-response": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" }, "update-notifier": { "version": "2.5.0", - "resolved": "", + "resolved": false, "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", "requires": { "boxen": "^1.2.1", @@ -16376,7 +16376,7 @@ }, "uri-js": { "version": "4.4.0", - "resolved": "", + "resolved": false, "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "requires": { "punycode": "^2.1.0" @@ -16384,14 +16384,14 @@ "dependencies": { "punycode": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, "url-parse-lax": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "requires": { "prepend-http": "^1.0.1" @@ -16399,17 +16399,17 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util-extend": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=" }, "util-promisify": { "version": "2.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", "requires": { "object.getownpropertydescriptors": "^2.0.3" @@ -16417,12 +16417,12 @@ }, "uuid": { "version": "3.3.3", - "resolved": "", + "resolved": false, "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "validate-npm-package-license": { "version": "3.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "requires": { "spdx-correct": "^3.0.0", @@ -16431,7 +16431,7 @@ }, "validate-npm-package-name": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", "requires": { "builtins": "^1.0.3" @@ -16439,7 +16439,7 @@ }, "verror": { "version": "1.10.0", - "resolved": "", + "resolved": false, "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { "assert-plus": "^1.0.0", @@ -16449,7 +16449,7 @@ }, "wcwidth": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "requires": { "defaults": "^1.0.3" @@ -16457,7 +16457,7 @@ }, "which": { "version": "1.3.1", - "resolved": "", + "resolved": false, "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" @@ -16465,12 +16465,12 @@ }, "which-module": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { "version": "1.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "requires": { "string-width": "^1.0.2" @@ -16478,7 +16478,7 @@ "dependencies": { "string-width": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", @@ -16490,7 +16490,7 @@ }, "widest-line": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "requires": { "string-width": "^2.1.1" @@ -16498,7 +16498,7 @@ }, "worker-farm": { "version": "1.7.0", - "resolved": "", + "resolved": false, "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "requires": { "errno": "~0.1.7" @@ -16506,7 +16506,7 @@ }, "wrap-ansi": { "version": "5.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "requires": { "ansi-styles": "^3.2.0", @@ -16516,17 +16516,17 @@ "dependencies": { "ansi-regex": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", @@ -16536,7 +16536,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" @@ -16546,12 +16546,12 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "2.4.3", - "resolved": "", + "resolved": false, "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "requires": { "graceful-fs": "^4.1.11", @@ -16561,27 +16561,27 @@ }, "xdg-basedir": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" }, "xtend": { "version": "4.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { "version": "4.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" }, "yallist": { "version": "3.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "14.2.3", - "resolved": "", + "resolved": false, "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", "requires": { "cliui": "^5.0.0", @@ -16599,12 +16599,12 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "find-up": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { "locate-path": "^3.0.0" @@ -16612,12 +16612,12 @@ }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "locate-path": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { "p-locate": "^3.0.0", @@ -16626,7 +16626,7 @@ }, "p-limit": { "version": "2.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "requires": { "p-try": "^2.0.0" @@ -16634,7 +16634,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { "p-limit": "^2.0.0" @@ -16642,12 +16642,12 @@ }, "p-try": { "version": "2.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "string-width": { "version": "3.1.0", - "resolved": "", + "resolved": false, "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", @@ -16657,7 +16657,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" @@ -16667,7 +16667,7 @@ }, "yargs-parser": { "version": "15.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", "requires": { "camelcase": "^5.0.0", @@ -16676,7 +16676,7 @@ "dependencies": { "camelcase": { "version": "5.3.1", - "resolved": "", + "resolved": false, "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" } } diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 289c5bc51..4af51b9a0 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -1,12 +1,12 @@ -import React = require("react"); -import { Utils } from "../../Utils"; -import "./InteractionUtils.scss"; +import React = require('react'); +import { Utils } from '../../Utils'; +import './InteractionUtils.scss'; export namespace InteractionUtils { - export const MOUSETYPE = "mouse"; - export const TOUCHTYPE = "touch"; - export const PENTYPE = "pen"; - export const ERASERTYPE = "eraser"; + export const MOUSETYPE = 'mouse'; + export const TOUCHTYPE = 'touch'; + export const PENTYPE = 'pen'; + export const ERASERTYPE = 'eraser'; const POINTER_PEN_BUTTON = -1; const REACT_POINTER_PEN_BUTTON = 0; @@ -19,24 +19,23 @@ export namespace InteractionUtils { readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[], readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent - ) { } + ) {} } - export interface MultiTouchEventDisposer { (): void; } + export interface MultiTouchEventDisposer { + (): void; + } /** * * @param element - element to turn into a touch target * @param startFunc - event handler, typically Touchable.onTouchStart (classes that inherit touchable can pass in this.onTouchStart) */ - export function MakeMultiTouchTarget( - element: HTMLElement, - startFunc: (e: Event, me: MultiTouchEvent) => void - ): MultiTouchEventDisposer { + export function MakeMultiTouchTarget(element: HTMLElement, startFunc: (e: Event, me: MultiTouchEvent) => void): MultiTouchEventDisposer { const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent>).detail); // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent>).detail) : undefined; // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent>).detail) : undefined; - element.addEventListener("dashOnTouchStart", onMultiTouchStartHandler); + element.addEventListener('dashOnTouchStart', onMultiTouchStartHandler); // if (onMultiTouchMoveHandler) { // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler); // } @@ -44,7 +43,7 @@ export namespace InteractionUtils { // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler); // } return () => { - element.removeEventListener("dashOnTouchStart", onMultiTouchStartHandler); + element.removeEventListener('dashOnTouchStart', onMultiTouchStartHandler); // if (onMultiTouchMoveHandler) { // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler); // } @@ -59,14 +58,11 @@ export namespace InteractionUtils { * @param element - element to add events to * @param func - function to add to the event */ - export function MakeHoldTouchTarget( - element: HTMLElement, - func: (e: Event, me: MultiTouchEvent) => void - ): MultiTouchEventDisposer { + export function MakeHoldTouchTarget(element: HTMLElement, func: (e: Event, me: MultiTouchEvent) => void): MultiTouchEventDisposer { const handler = (e: Event) => func(e, (e as CustomEvent>).detail); - element.addEventListener("dashOnTouchHoldStart", handler); + element.addEventListener('dashOnTouchHoldStart', handler); return () => { - element.removeEventListener("dashOnTouchHoldStart", handler); + element.removeEventListener('dashOnTouchHoldStart', handler); }; } @@ -89,71 +85,108 @@ export namespace InteractionUtils { return myTouches; } - export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, - color: string, width: number, strokeWidth: number, lineJoin: string, lineCap: string, bezier: string, fill: string, arrowStart: string, arrowEnd: string, - markerScale: number, dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, opacity: number, nodefs: boolean, - downHdlr?: ((e: React.PointerEvent) => void)) { + export function CreatePolyline( + points: { X: number; Y: number }[], + left: number, + top: number, + color: string, + width: number, + strokeWidth: number, + lineJoin: string, + lineCap: string, + bezier: string, + fill: string, + arrowStart: string, + arrowEnd: string, + markerScale: number, + dash: string | undefined, + scalex: number, + scaley: number, + shape: string, + pevents: string, + opacity: number, + nodefs: boolean, + downHdlr?: (e: React.PointerEvent) => void + ) { const pts = shape ? makePolygon(shape, points) : points; if (isNaN(scalex)) scalex = 1; if (isNaN(scaley)) scaley = 1; - 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 ? "" : (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 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 ? '' : (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; const defGuid = Utils.GenerateGuid(); - const Tag = (bezier ? "path" : "polyline") as keyof JSX.IntrinsicElements; + const Tag = (bezier ? 'path' : 'polyline') as keyof JSX.IntrinsicElements; const markerStrokeWidth = strokeWidth / 2; - const arrowWidthFactor = 3 * (markerScale || 0.5);// used to be 1.5 + const arrowWidthFactor = 3 * (markerScale || 0.5); // used to be 1.5 const arrowLengthFactor = 5 * (markerScale || 0.5); const arrowNotchFactor = 2 * (markerScale || 0.5); - return ( {/* setting the svg fill sets the arrowStart fill */} - {nodefs ? (null) : - {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : - - - } - {arrowStart !== "arrow" ? (null) : - - - } - {arrowEnd !== "arrow" ? (null) : - - - } - } - - - - ); + return ( + + {' '} + {/* setting the svg fill sets the arrowStart fill */} + {nodefs ? null : ( + + {arrowStart !== 'dot' && arrowEnd !== 'dot' ? null : ( + + + + )} + {arrowStart !== 'arrow' ? null : ( + + + + )} + {arrowEnd !== 'arrow' ? null : ( + + + + )} + + )} + + + ); } - export function makePolygon(shape: string, points: { X: number, Y: number }[]) { + export function makePolygon(shape: string, points: { X: number; Y: number }[]) { if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) { //pointer is up (first and last points are the same) - if (shape === "arrow" || shape === "line" || shape === "circle") { + if (shape === 'arrow' || shape === 'line' || shape === 'circle') { //if arrow or line, the two end points should be the starting and the ending point var left = points[0].X; var top = points[0].Y; @@ -175,7 +208,7 @@ export namespace InteractionUtils { left = points[0].X; bottom = points[points.length - 1].Y; top = points[0].Y; - if (shape !== "arrow" && shape !== "line" && shape !== "circle") { + if (shape !== 'arrow' && shape !== 'line' && shape !== 'circle') { //switch left/right and top/bottom if needed if (left > right) { const temp = right; @@ -191,14 +224,13 @@ export namespace InteractionUtils { } points = []; switch (shape) { - case "rectangle": + case 'rectangle': points.push({ X: left, Y: top }); points.push({ X: right, Y: top }); points.push({ X: right, Y: bottom }); points.push({ X: left, Y: bottom }); points.push({ X: left, Y: top }); - return points; - case "triangle": + case 'triangle': // points.push({ X: left, Y: bottom }); // points.push({ X: right, Y: bottom }); // points.push({ X: (right + left) / 2, Y: top }); @@ -219,62 +251,39 @@ export namespace InteractionUtils { points.push({ X: left, Y: bottom }); points.push({ X: left, Y: bottom }); - - - return points; - case "circle": + case 'circle': const centerX = (Math.max(left, right) + Math.min(left, right)) / 2; const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2; const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom)); if (centerX - Math.min(left, right) < centerY - Math.min(top, bottom)) { for (var y = Math.min(top, bottom); y < Math.max(top, bottom); y++) { - const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + const x = Math.sqrt(Math.pow(radius, 2) - Math.pow(y - centerY, 2)) + centerX; points.push({ X: x, Y: y }); } for (var y = Math.max(top, bottom); y > Math.min(top, bottom); y--) { - const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + const x = Math.sqrt(Math.pow(radius, 2) - Math.pow(y - centerY, 2)) + centerX; const newX = centerX - (x - centerX); points.push({ X: newX, Y: y }); } - points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(top, bottom) - centerY), 2))) + centerX, Y: Math.min(top, bottom) }); + points.push({ X: Math.sqrt(Math.pow(radius, 2) - Math.pow(Math.min(top, bottom) - centerY, 2)) + centerX, Y: Math.min(top, bottom) }); } else { for (var x = Math.min(left, right); x < Math.max(left, right); x++) { - const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY; + const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY; points.push({ X: x, Y: y }); } for (var x = Math.max(left, right); x > Math.min(left, right); x--) { - const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY; + const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY; const newY = centerY - (y - centerY); points.push({ X: x, Y: newY }); } - points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - (Math.pow((Math.min(left, right) - centerX), 2))) + centerY }); + points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(Math.min(left, right) - centerX, 2)) + centerY }); } - return points; - // case "arrow": - // const x1 = left; - // const y1 = top; - // const x2 = right; - // const y2 = bottom; - // const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + (Math.pow(Math.abs(y1 - y2), 2))); - // const L2 = L1 / 5; - // const angle = 0.785398; - // const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle)); - // const y3 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) - (x1 - x2) * Math.sin(angle)); - // const x4 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle)); - // const y4 = y2 + (L2 / L1) * ((y1 - y2) * Math.cos(angle) + (x1 - x2) * Math.sin(angle)); - // points.push({ X: x1, Y: y1 }); - // points.push({ X: x2, Y: y2 }); - // points.push({ X: x3, Y: y3 }); - // points.push({ X: x4, Y: y4 }); - // points.push({ X: x2, Y: y2 }); - // return points; - case "line": + case 'line': points.push({ X: left, Y: top }); points.push({ X: right, Y: bottom }); return points; - default: - return points; } + return points; } /** * Returns whether or not the pointer event passed in is of the type passed in @@ -284,11 +293,14 @@ export namespace InteractionUtils { export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { switch (type) { // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2 - case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0); - case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); + case PENTYPE: + return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0); + case ERASERTYPE: + return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); case TOUCHTYPE: return e.pointerType === TOUCHTYPE; - default: return e.pointerType === type; + default: + return e.pointerType === type; } } @@ -305,7 +317,7 @@ export namespace InteractionUtils { * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point) * @param pts - n-arbitrary long list of points */ - export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } { + export function CenterPoint(pts: React.Touch[]): { X: number; Y: number } { const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length; const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length; return { X: centerX, Y: centerY }; @@ -324,9 +336,9 @@ export namespace InteractionUtils { const newDist = TwoPointEuclidist(pt1, pt2); /** if they have the same sign, then we are either pinching in or out. - * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch) - * so that it can still pan without freaking out - */ + * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch) + * so that it can still pan without freaking out + */ if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) { return Math.sign(oldDist - newDist); } @@ -372,8 +384,6 @@ export namespace InteractionUtils { // These might not be very useful anymore, but I'll leave them here for now -syip2 { - - /** * Returns the type of Touch Interaction from a list of points. * Also returns any data that is associated with a Touch Interaction diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss index 664f2448b..bed7caf7f 100644 --- a/src/client/views/InkStroke.scss +++ b/src/client/views/InkStroke.scss @@ -1,22 +1,23 @@ .inkstroke-UI { - // transform-origin: top left; - position: absolute; - overflow: visible; - pointer-events: none; - z-index: 2001; // 1 higher than documentdecorations - - svg:not(:root) { - overflow: visible !important; + // transform-origin: top left; position: absolute; - left:0; - top:0; - } + overflow: visible; + pointer-events: none; + z-index: 2001; // 1 higher than documentdecorations + + svg:not(:root) { + overflow: visible !important; + position: absolute; + left: 0; + top: 0; + } } .inkStroke-wrapper { display: flex; align-items: center; height: 100%; + transition: inherit; .inkStroke { mix-blend-mode: multiply; stroke-linejoin: round; @@ -26,8 +27,10 @@ width: 100%; height: 100%; pointer-events: none; + transition: inherit; svg:not(:root) { overflow: visible !important; + transition: inherit; } } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 821e2f739..b32c9d54c 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -4,6 +4,7 @@ import { Doc, NumListCast, Opt } from '../../fields/Doc'; import { InkData, InkField, InkTool, PointData } from '../../fields/InkField'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; +import { ComputedField } from '../../fields/ScriptField'; import { Cast, NumCast } from '../../fields/Types'; import { Point } from '../../pen-gestures/ndollar'; import { DocumentType } from '../documents/DocumentTypes'; @@ -11,6 +12,7 @@ import { FitOneCurve } from '../util/bezierFit'; import { DocumentManager } from '../util/DocumentManager'; import { undoBatch } from '../util/UndoManager'; import { InkingStroke } from './InkingStroke'; +import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView'; import { DocumentView } from './nodes/DocumentView'; export class InkStrokeProperties { @@ -65,7 +67,14 @@ export class InkStrokeProperties { 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); + if (doc.activeFrame !== undefined) { + doc.data = ComputedField.MakeInterpolated('data', 'activeFrame', doc, NumCast(doc.activeFrame)); + const findexed = Cast(doc[`data-indexed`], listSpec(InkField), []).slice(); + findexed[NumCast(doc.activeFrame)] = new InkField(newPoints); + doc[`data-indexed`] = new List(findexed); + } else { + Doc.GetProto(doc).data = new InkField(newPoints); + } appliedFunc = true; } } @@ -347,7 +356,6 @@ export class InkStrokeProperties { const deltaX = snapData.nearestPt.X - ink[controlIndex].X; const deltaY = snapData.nearestPt.Y - ink[controlIndex].Y; const res = this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex, ink.slice()); - console.log('X = ' + snapData.nearestPt.X + ' ' + snapData.nearestPt.Y); return res; } } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 2671aea56..52efbdfd7 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -401,13 +401,13 @@ export class InkingStroke extends ViewBoxBaseComponent() { inkTop, highlightColor, inkStrokeWidth, - fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0), + Math.max(5, fillColor && closed && highlightIndex ? highlightIndex / 2 : inkStrokeWidth + (fillColor ? (closed ? 0 : highlightIndex + 2) : 0)), StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(this.layoutDoc.strokeBezier), !closed ? 'none' : fillColor === 'transparent' || suppressFill ? 'none' : fillColor, - startMarker, - endMarker, + '', + '', markerScale, undefined, inkScaleX, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 490e9e025..d2b0e10c1 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -172,6 +172,7 @@ export class MainView extends React.Component { 'curPage', 'viewType', 'chromeHidden', + 'currentFrame', 'width', 'nativeWidth', ]); // can play with these fields on someone else's diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 6ea697a2f..9aaaf1e68 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -99,6 +99,7 @@ border-radius: inherit; width: 100%; height: 100%; + transition: inherit; .sharingIndicator { height: 30px; -- cgit v1.2.3-70-g09d2 From d8f2a45a95a14151d3484c3c14f72d217a818786 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 22 Aug 2022 12:50:52 -0400 Subject: made inkMasks animate when they are turned on /off. fixed inkStrokes to be interpolated when they have an activeFrame set. --- src/client/util/CurrentUserUtils.ts | 4 ++-- src/client/views/DocumentDecorations.tsx | 4 ++-- src/client/views/InkStrokeProperties.ts | 1 - src/client/views/InkingStroke.tsx | 22 +++++++++++++++---- .../CollectionFreeFormLayoutEngines.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.scss | 9 ++++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 9 ++++---- src/client/views/nodes/DocumentView.tsx | 1 - src/client/views/nodes/button/FontIconBox.tsx | 25 +++++++++++----------- 9 files changed, 47 insertions(+), 30 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f7d072d80..20c57c617 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -633,7 +633,7 @@ export class CurrentUserUtils { { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }}, { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} }, { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} }, - { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}}, + { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}}, { title: "Dictate",toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", scripts: {onClick:'{ return toggleDictation(_readOnly_);}'}}, ]; } @@ -676,7 +676,7 @@ export class CurrentUserUtils { CollectionViewType.Grid, CollectionViewType.NoteTaking]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, - { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20, scripts: {}}, + { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20, scripts: {}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 6d1397395..ab77af0f4 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -203,8 +203,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (this._deleteAfterIconify) { views.forEach(iconView => { Doc.setNativeView(iconView.props.Document); - if (iconView.props.Document.isInkMask && iconView.props.Document.activeFrame !== undefined) { - iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMaks to be "turned off" without removing them from the collection which allows them to function properly in a presenation. + if (iconView.props.Document.activeFrame) { + iconView.props.Document.opacity = 0; // bcz: hacky ... allows inkMasks and other documents to be "turned off" without removing them from the animated collection which allows them to function properly in a presenation. } else { iconView.props.removeDocument?.(iconView.props.Document); } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index b32c9d54c..1f5f16592 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -68,7 +68,6 @@ export class InkStrokeProperties { doc.x = oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale; doc.y = oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale; if (doc.activeFrame !== undefined) { - doc.data = ComputedField.MakeInterpolated('data', 'activeFrame', doc, NumCast(doc.activeFrame)); const findexed = Cast(doc[`data-indexed`], listSpec(InkField), []).slice(); findexed[NumCast(doc.activeFrame)] = new InkField(newPoints); doc[`data-indexed`] = new List(findexed); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 52efbdfd7..520d40abf 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -25,7 +25,7 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, HeightSym, WidthSym } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; -import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, FieldValue, NumCast, RTFCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; import { OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils'; import { CognitiveServices } from '../cognitive_services/CognitiveServices'; @@ -45,6 +45,9 @@ import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { INK_MASK_SIZE } from './global/globalCssVariables.scss'; import './InkStroke.scss'; import Color = require('color'); +import { ComputedField } from '../../fields/ScriptField'; +import { listSpec } from '../../fields/Schema'; +import { List } from '../../fields/List'; @observer export class InkingStroke extends ViewBoxBaseComponent() { @@ -56,7 +59,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y; } private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated - private _selDisposer?: IReactionDisposer; + private _disposers: { [key: string]: IReactionDisposer } = {}; @observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight) @observable _nearestT?: number; // nearest t value within the nearest Bezier segment " @@ -64,13 +67,24 @@ export class InkingStroke extends ViewBoxBaseComponent() { componentDidMount() { this.props.setContentView?.(this); - this._selDisposer = reaction( + this._disposers.activeFrame = reaction( + () => this.rootDoc.activeFrame !== undefined && !(ComputedField.WithoutComputed(() => FieldValue(this.rootDoc[this.fieldKey])) instanceof ComputedField), + () => { + const newPoints = Cast(this.rootDoc[this.fieldKey], InkField, null).inkData; + this.rootDoc[this.fieldKey] = ComputedField.MakeInterpolated(this.fieldKey, 'activeFrame', this.rootDoc, NumCast(this.rootDoc.activeFrame)); + const findexed = Cast(this.rootDoc[`data-indexed`], listSpec(InkField), []).slice(); + findexed[NumCast(this.rootDoc.activeFrame)] = new InkField(newPoints); + this.rootDoc[this.fieldKey + '-indexed'] = new List(findexed); + }, + { fireImmediately: true } + ); + this._disposers.selfDisper = reaction( () => this.props.isSelected(), // react to stroke being deselected by turning off ink handles selected => !selected && (InkStrokeProperties.Instance._controlButton = false) ); } componentWillUnmount() { - this._selDisposer?.(); + Object.keys(this._disposers).forEach(key => this._disposers[key]()); } // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index ee01c341b..b210e7d9a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -45,7 +45,7 @@ export interface PoolData { export interface ViewDefResult { ele: JSX.Element; bounds?: ViewDefBounds; - inkMask?: boolean; + inkMask?: number; //sort elements into either the mask layer (which has a mixedBlendMode appropriate for transparent masks), or the regular documents layer; -1 = no mask, 0 = mask layer but stroke is transprent (hidden), >0 = mask layer and not hidden } function toLabel(target: FieldResult) { if (typeof target === 'number' || Number(target)) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 010132aa5..d80fcdfc3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -20,15 +20,20 @@ pointer-events: none; } +.collectionfreeformview-mask-empty, .collectionfreeformview-mask { - mix-blend-mode: multiply; z-index: 5000; width: $INK_MASK_SIZE; height: $INK_MASK_SIZE; transform: translate($INK_MASK_SIZE_HALF, $INK_MASK_SIZE_HALF); - background-color: rgba(0, 0, 0, 0.7); pointer-events: none; position: absolute; + background-color: transparent; + transition: background-color 1s ease 0s; +} +.collectionfreeformview-mask { + mix-blend-mode: multiply; + background-color: rgba(0, 0, 0, 0.7); } .collectionfreeformview-viewdef { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0fd326091..1d518076a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -119,9 +119,9 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z && ele.inkMask).map(ele => ele.ele); - const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && !ele.inkMask).map(ele => ele.ele); - if (viewsMask.length) renderableEles.push(
{viewsMask}
); + const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1).map(ele => ele.ele); + const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask === -1).map(ele => ele.ele); + if (viewsMask.length) renderableEles.push(
(ele.inkMask ?? 0) > 0) ? '' : '-empty'}`}>{viewsMask}
); return renderableEles; } @computed get fitToContentVals() { @@ -251,7 +251,6 @@ export class CollectionFreeFormView extends CollectionSubView { ref={this.ContentRef} style={{ transition: this.props.dataTransition, - //position: this.props.Document.isInkMask ? 'absolute' : undefined, transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, height: diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index cb68c1ac3..fc5bf86f4 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -90,9 +90,11 @@ export class FontIconBox extends DocComponent() { @computed get label() { return StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); } - @computed get icon() { - return StrCast(this.dataDoc.icon, 'user') as any; - } + Icon = (color: string) => { + const icon = StrCast(this.dataDoc.icon, 'user') as any; + const trailsIcon = () => ; + return !icon ? null : icon === 'pres-trail' ? trailsIcon() : ; + }; @computed get dropdown() { return BoolCast(this.rootDoc.dropDownOpen); } @@ -228,7 +230,7 @@ export class FontIconBox extends DocComponent() { className={`menuButton ${this.type} ${active}`} style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}> - + {this.Icon(color)} {!this.label || !FontIconBox.GetShowLabels() ? null : (
{' '} @@ -384,7 +386,7 @@ export class FontIconBox extends DocComponent() { style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(() => (this.colorPickerClosed = !this.colorPickerClosed))} onPointerDown={e => e.stopPropagation()}> - + {this.Icon(color)}
{label} {/* {dropdownCaret} */} @@ -436,7 +438,7 @@ export class FontIconBox extends DocComponent() { } else { return (
- + {this.Icon(color)} {label}
); @@ -453,7 +455,7 @@ export class FontIconBox extends DocComponent() { return (
- + {this.Icon(color)} {!this.label || !FontIconBox.GetShowLabels() ? null : (
{' '} @@ -506,7 +508,7 @@ export class FontIconBox extends DocComponent() { case ButtonType.TextButton: button = (
- + {this.Icon(color)} {StrCast(this.rootDoc.buttonText) ?
{StrCast(this.rootDoc.buttonText)}
: null} {label}
@@ -531,7 +533,7 @@ export class FontIconBox extends DocComponent() { case ButtonType.ToolButton: button = (
- + {this.Icon(color)} {label}
); @@ -543,16 +545,15 @@ export class FontIconBox extends DocComponent() { case ButtonType.ClickButton: button = (
- + {this.Icon(color)} {label}
); break; case ButtonType.MenuButton: - const trailsIcon = ; button = (
- {this.icon === 'pres-trail' ? trailsIcon : } + {this.Icon(color)} {menuLabel}
-- cgit v1.2.3-70-g09d2 From 440f5c296a599d5c3f9ed781b58c5b00da6edb5a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 22 Aug 2022 17:18:10 -0400 Subject: added animation of background color, and streamlined/extended a variety of props-related things. --- src/client/util/CurrentUserUtils.ts | 6 +-- src/client/views/DocComponent.tsx | 14 ------- src/client/views/StyleProvider.tsx | 9 +++-- .../CollectionFreeFormLayoutEngines.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 33 ++++++++-------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 30 ++++++++++++--- src/client/views/nodes/FieldView.tsx | 11 ++++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 45 ++++++++++------------ src/fields/Doc.ts | 14 +++++++ src/fields/ScriptField.ts | 12 +++++- 10 files changed, 102 insertions(+), 74 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 20c57c617..17d58595c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -235,9 +235,9 @@ export class CurrentUserUtils { const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text", layout: "" + - ` ` + - " " + - ` Metadata` + + ` ` + + " " + + ` Metadata` + "" }, "header"); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 280ca8a8c..886dd974b 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -132,20 +132,6 @@ export function ViewBoxAnnotatableComponent

() lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result; - styleFromLayoutString = (scale: number) => { - const style: { [key: string]: any } = {}; - const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'background', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position']; - const replacer = (match: any, expr: string, offset: any, string: any) => { - // bcz: this executes a script to convert a property expression string: { script } into a value - return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? ''; - }; - divKeys.map((prop: string) => { - const p = (this.props as any)[prop]; - typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer)); - }); - return style; - }; - protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @computed public get annotationKey() { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3bd4f5152..c0ba170c6 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -40,7 +40,8 @@ export enum StyleProp { JitterRotation = 'jitterRotation', // whether documents should be randomly rotated BorderPath = 'customBorder', // border path for document view FontSize = 'fontSize', // size of text font - FontFamily = 'fontFamily', // size of text font + FontFamily = 'fontFamily', // font family of text + FontWeight = 'fontWeight', // font weight of text } function darkScheme() { @@ -117,9 +118,11 @@ export function DefaultStyleProvider(doc: Opt, props: Opt(); @observable _marqueeRef = React.createRef(); @observable _marqueeViewRef = React.createRef(); - @observable _keyframeEditing = false; @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. @computed get views() { @@ -185,8 +184,6 @@ export class CollectionFreeFormView extends CollectionSubView (this._keyframeEditing = set); - getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); @@ -272,15 +269,15 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1)); const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); - const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)]; + const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)]; for (let i = 0; i < docDragData.droppedDocuments.length; i++) { const d = docDragData.droppedDocuments[i]; const layoutDoc = Doc.Layout(d); if (this.Document._currentFrame !== undefined) { CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false); const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); - vals.x = x + (vals.x || 0) - dropPos[0]; - vals.y = y + (vals.y || 0) - dropPos[1]; + vals.x = x + NumCast(vals.x) - dropPos[0]; + vals.y = y + NumCast(vals.y) - dropPos[1]; vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined; CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals); } else { @@ -1317,22 +1314,22 @@ export class CollectionFreeFormView extends CollectionSubView { x: number; y: number; zIndex?: number; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; + dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined; renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; @@ -32,6 +32,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @observer export class CollectionFreeFormDocumentView extends DocComponent() { public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames + public static animStringFields = ['backgroundColor']; // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; get displayName() { @@ -41,16 +42,19 @@ export class CollectionFreeFormDocumentView extends DocComponent, property: string) => { if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children + if (property === StyleProp.BackgroundColor) { + return this.BackgroundColor; // only change the opacity for this specific document, not its children + } return this.props.styleProvider?.(doc, props, property); }; @@ -77,6 +84,13 @@ export class CollectionFreeFormDocumentView extends DocComponent }); } + public static getStringValues(doc: Doc, time: number) { + return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => { + p[val] = Cast(`${val}-indexed`, listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string); + return p; + }, {} as { [val: string]: Opt }); + } + public static setValues(time: number, d: Doc, vals: { [val: string]: Opt }) { const timecode = Math.round(time); Object.keys(vals).forEach(val => { @@ -95,6 +109,10 @@ export class CollectionFreeFormDocumentView extends DocComponent { + const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); + }); }) ); setTimeout( @@ -142,6 +160,7 @@ export class CollectionFreeFormDocumentView extends DocComponent(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1))); } CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolated(val, 'activeFrame', doc, currTimecode))); + CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode))); doc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0'); doc.dataTransition = 'inherit'; }); @@ -189,7 +208,6 @@ export class CollectionFreeFormDocumentView extends DocComponent number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps onBrowseClick?: () => ScriptField | undefined; onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined; + pointerEvents?: () => Opt; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) - pointerEvents?: () => Opt; + // See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields + // Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields + backgroundColor?: string; + color?: string; fontSize?: number; height?: number; width?: number; - background?: string; - color?: string; xPadding?: number; yPadding?: number; + noSidebar?: boolean; + dontScale?: boolean; + dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field) } @observer diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f61533619..81ac45521 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -11,7 +11,7 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { DateField } from '../../../../fields/DateField'; -import { AclAdmin, AclAugment, AclEdit, AclReadonly, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; @@ -35,6 +35,7 @@ import { SnappingManager } from '../../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionStackingView } from '../../collections/CollectionStackingView'; +import { CollectionTreeView } from '../../collections/CollectionTreeView'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; @@ -44,6 +45,7 @@ import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; +import { DocumentViewInternal } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { LinkDocPreview } from '../LinkDocPreview'; import { DashDocCommentView } from './DashDocCommentView'; @@ -61,25 +63,14 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); -import { text } from 'body-parser'; -import { CollectionTreeView } from '../../collections/CollectionTreeView'; -import { DocumentViewInternal } from '../DocumentView'; const translateGoogleApi = require('translate-google-api'); -export interface FormattedTextBoxProps { - makeLink?: () => Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text - xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView - yPadding?: number; - noSidebar?: boolean; - dontScale?: boolean; - dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field) -} export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent() { +export class FormattedTextBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } @@ -1843,18 +1834,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent= 10 ? '-selected' : ''; - const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., - return styleFromString?.height === '0px' ? null : ( + const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., + return styleFromLayoutString?.height === '0px' ? null : (

this.props.isContentActive() && e.stopPropagation()} style={{ - transform: this.props.dontScale ? undefined : `scale(${scale})`, - transformOrigin: this.props.dontScale ? undefined : 'top left', - width: this.props.dontScale ? undefined : `${100 / scale}%`, - height: this.props.dontScale ? undefined : `${100 / scale}%`, + ...(this.props.dontScale + ? {} + : { + transform: `scale(${scale})`, + transformOrigin: 'top left', + width: `${100 / scale}%`, + height: `${100 / scale}%`, + }), + transition: 'inherit', // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, - ...styleFromString, + color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), + fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), + fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily), + fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight), + ...styleFromLayoutString, }}>
Doc.AreProtosEqual(val, doc)) !== -1; } + export function styleFromLayoutString(rootDoc: Doc, layoutDoc: Doc, props: any, scale: number) { + const style: { [key: string]: any } = {}; + const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position']; + const replacer = (match: any, expr: string, offset: any, string: any) => { + // bcz: this executes a script to convert a property expression string: { script } into a value + return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: rootDoc, this: layoutDoc, scale }).result?.toString() ?? ''; + }; + divKeys.map((prop: string) => { + const p = props[prop]; + typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer)); + }); + return style; + } + // prettier-ignore export function toIcon(doc?: Doc, isOpen?: boolean) { switch (StrCast(doc?.type)) { diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 68fb45987..0fd992d3b 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -9,7 +9,7 @@ import { Doc, Field, Opt } from './Doc'; import { Copy, Id, ToScriptString, ToString } from './FieldSymbols'; import { List } from './List'; import { ObjectField } from './ObjectField'; -import { Cast, NumCast } from './Types'; +import { Cast, NumCast, StrCast } from './Types'; import { Plugins } from './util'; function optional(propSchema: PropSchema) { @@ -199,6 +199,16 @@ export class ComputedField extends ScriptField { const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {}); return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; } + public static MakeInterpolatedString(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) { + if (!doc[`${fieldKey}-indexed`]) { + const flist = new List(numberRange(curTimecode + 1).map(i => undefined) as any as string[]); + flist[curTimecode] = StrCast(doc[fieldKey]); + doc[`${fieldKey}-indexed`] = flist; + } + const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {}); + const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {}); + return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; + } } export namespace ComputedField { let useComputed = true; -- cgit v1.2.3-70-g09d2 From 653afba6635d676ec4fcdfa649360ca26c91cb88 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 22 Aug 2022 17:23:21 -0400 Subject: from last --- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- src/client/views/nodes/DocumentContentsView.tsx | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a807ba4ea..b34dad226 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1314,7 +1314,7 @@ export class CollectionFreeFormView extends CollectionSubView { @observer export class DocumentContentsView extends React.Component< - DocumentViewProps & - FormattedTextBoxProps & { - isSelected: (outsideReaction: boolean) => boolean; - select: (ctrl: boolean) => void; - NativeDimScaling?: () => number; - setHeight?: (height: number) => void; - layoutKey: string; - } + DocumentViewProps & { + isSelected: (outsideReaction: boolean) => boolean; + select: (ctrl: boolean) => void; + NativeDimScaling?: () => number; + setHeight?: (height: number) => void; + layoutKey: string; + } > { @computed get layout(): string { TraceMobx(); -- cgit v1.2.3-70-g09d2 From 267bb35a555ac0f8b67041346213d9a06386785f Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 23 Aug 2022 11:57:58 -0400 Subject: added color to animated properties. changed doc decorations to stop before menu bar. changed color of tab bar so that doc decorations is visible when overlapping. --- src/client/views/InkingStroke.tsx | 3 ++- src/client/views/MainView.tsx | 4 ++-- src/client/views/_nodeModuleOverrides.scss | 3 ++- .../CollectionFreeFormLayoutEngines.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 14 ++++++++++-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 25 +++++++++++++--------- 6 files changed, 34 insertions(+), 16 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 520d40abf..ceaabd0e1 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -48,6 +48,7 @@ import Color = require('color'); import { ComputedField } from '../../fields/ScriptField'; import { listSpec } from '../../fields/Schema'; import { List } from '../../fields/List'; +import { StyleProp } from './StyleProvider'; @observer export class InkingStroke extends ViewBoxBaseComponent() { @@ -370,7 +371,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { const closed = InkingStroke.IsClosed(inkData); const isInkMask = BoolCast(this.layoutDoc.isInkMask); const fillColor = isInkMask ? '#aaaaaa' : StrCast(this.layoutDoc.fillColor, 'transparent'); - const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : StrCast(this.layoutDoc.color); + const strokeColor = !closed && fillColor && fillColor !== 'transparent' ? fillColor : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color) ?? StrCast(this.layoutDoc.color); // bcz: Hack!! Not really sure why, but having fractional values for width/height of mask ink strokes causes the dragging clone (see DragManager) to be offset from where it should be. if (isInkMask && (this.layoutDoc[WidthSym]() !== Math.round(this.layoutDoc[WidthSym]()) || this.layoutDoc[HeightSym]() !== Math.round(this.layoutDoc[HeightSym]()))) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d2b0e10c1..06be4d194 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -981,7 +981,7 @@ export class MainView extends React.Component { - + {this._hideUI ? null : } {LinkDescriptionPopup.descriptionPopup ? : null} @@ -994,7 +994,7 @@ export class MainView extends React.Component { default: return ( <> -
+
{this.mainDashboardArea} diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss index 17eff022f..b1cce8705 100644 --- a/src/client/views/_nodeModuleOverrides.scss +++ b/src/client/views/_nodeModuleOverrides.scss @@ -44,7 +44,8 @@ div .lm_header { position: absolute; width: calc(100% - 60px); overflow: scroll; - background: $dark-gray; + background: #6b6b6b6b; //$dark-gray; + border-radius: 5px; } .lm_tab { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index d84717b95..89cc22d07 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -35,6 +35,7 @@ export interface PoolData { width?: number; height?: number; backgroundColor?: string; + color?: string; opacity?: number; transition?: string; highlight?: boolean; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0234d303f..03beaf65e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1318,12 +1318,13 @@ export class CollectionFreeFormView extends CollectionSubView { x: number; y: number; zIndex?: number; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; + dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined; renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; @@ -32,12 +32,14 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { @observer export class CollectionFreeFormDocumentView extends DocComponent() { public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames - public static animStringFields = ['backgroundColor']; // fields that are configured to be animatable using animation frames + public static animStringFields = ['backgroundColor', 'color']; // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; get displayName() { + // this makes mobx trace() statements more descriptive return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')'; - } // this makes mobx trace() statements more descriptive + } + get transform() { return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`; } @@ -56,12 +58,12 @@ export class CollectionFreeFormDocumentView extends DocComponent; - } @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); } @@ -70,10 +72,13 @@ export class CollectionFreeFormDocumentView extends DocComponent, property: string) => { - if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children - if (property === StyleProp.BackgroundColor) { - return this.BackgroundColor; // only change the opacity for this specific document, not its children - } + if (doc === this.layoutDoc) + // prettier-ignore + switch (property) { + case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children + case StyleProp.BackgroundColor: return this.BackgroundColor; + case StyleProp.Color: return this.Color; + } return this.props.styleProvider?.(doc, props, property); }; -- cgit v1.2.3-70-g09d2 From cebe0db46f1387c371865894eb0788921cd5e7c1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 23 Aug 2022 21:46:48 -0400 Subject: changed dragging off Key column header to create a view of the field, not a template --- src/client/views/nodes/KeyValueBox.tsx | 272 +++++++++++++++++++-------------- 1 file changed, 155 insertions(+), 117 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 4b1fbaf7d..d9f46509e 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -1,34 +1,37 @@ - -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, Field, FieldResult } from "../../../fields/Doc"; -import { List } from "../../../fields/List"; -import { RichTextField } from "../../../fields/RichTextField"; -import { listSpec } from "../../../fields/Schema"; -import { ComputedField, ScriptField } from "../../../fields/ScriptField"; -import { Cast, FieldValue, NumCast } from "../../../fields/Types"; -import { ImageField } from "../../../fields/URLField"; -import { Docs } from "../../documents/Documents"; -import { SetupDrag } from "../../util/DragManager"; -import { CompiledScript, CompileScript, ScriptOptions } from "../../util/Scripting"; -import { undoBatch } from "../../util/UndoManager"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, Field, FieldResult } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; +import { RichTextField } from '../../../fields/RichTextField'; +import { listSpec } from '../../../fields/Schema'; +import { ComputedField, ScriptField } from '../../../fields/ScriptField'; +import { Cast, DocCast, FieldValue, NumCast } from '../../../fields/Types'; +import { ImageField } from '../../../fields/URLField'; +import { Docs } from '../../documents/Documents'; +import { SetupDrag } from '../../util/DragManager'; +import { CompiledScript, CompileScript, ScriptOptions } from '../../util/Scripting'; +import { undoBatch } from '../../util/UndoManager'; import { FieldView, FieldViewProps } from './FieldView'; -import "./KeyValueBox.scss"; -import { KeyValuePair } from "./KeyValuePair"; -import React = require("react"); -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import e = require("express"); +import './KeyValueBox.scss'; +import { KeyValuePair } from './KeyValuePair'; +import React = require('react'); +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import e = require('express'); +import { FormattedTextBox } from './formattedText/FormattedTextBox'; +import { ImageBox } from './ImageBox'; export type KVPScript = { script: CompiledScript; - type: "computed" | "script" | false; + type: 'computed' | 'script' | false; onDelegate: boolean; }; @observer export class KeyValueBox extends React.Component { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(KeyValueBox, fieldStr); } + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(KeyValueBox, fieldStr); + } private _mainCont = React.createRef(); private _keyHeader = React.createRef(); @@ -37,8 +40,12 @@ export class KeyValueBox extends React.Component { @observable private rows: KeyValuePair[] = []; - @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); } - get fieldDocToLayout() { return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; } + @computed get splitPercentage() { + return NumCast(this.props.Document.schemaSplitPercentage, 50); + } + get fieldDocToLayout() { + return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; + } @action onEnterKey = (e: React.KeyboardEvent): void => { @@ -46,19 +53,19 @@ export class KeyValueBox extends React.Component { e.stopPropagation(); if (this._keyInput.current?.value && this._valInput.current?.value && this.fieldDocToLayout) { if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput.current.value, this._valInput.current.value)) { - this._keyInput.current.value = ""; - this._valInput.current.value = ""; + this._keyInput.current.value = ''; + this._valInput.current.value = ''; document.body.focus(); } } } - } + }; public static CompileKVPScript(value: string): KVPScript | undefined { - const eq = value.startsWith("="); + const eq = value.startsWith('='); value = eq ? value.substr(1) : value; - const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false; + const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith(';=') ? 'script' : false; value = dubEq ? value.substr(2) : value; - const options: ScriptOptions = { addReturn: true, params: { this: Doc.name, self: Doc.name, _last_: "any", _readOnly_: "boolean" }, editable: false }; + const options: ScriptOptions = { addReturn: true, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false }; if (dubEq) options.typecheck = false; const script = CompileScript(value, options); return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq }; @@ -67,11 +74,11 @@ export class KeyValueBox extends React.Component { public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean { const { script, type, onDelegate } = kvpScript; //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates - const target = forceOnDelegate || onDelegate || key.startsWith("_") ? doc : doc.proto || doc; + const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : doc.proto || doc; let field: Field; - if (type === "computed") { + if (type === 'computed') { field = new ComputedField(script); - } else if (type === "script") { + } else if (type === 'script') { field = new ScriptField(script); } else { const res = script.run({ this: target }, console.log); @@ -96,7 +103,7 @@ export class KeyValueBox extends React.Component { if (e.buttons === 1 && this.props.isSelected(true)) { e.stopPropagation(); } - } + }; onPointerWheel = (e: React.WheelEvent): void => e.stopPropagation(); rowHeight = () => 30; @@ -104,7 +111,11 @@ export class KeyValueBox extends React.Component { @computed get createTable() { const doc = this.fieldDocToLayout; if (!doc) { - return Loading...; + return ( + + Loading... + + ); } const realDoc = doc; @@ -122,83 +133,102 @@ export class KeyValueBox extends React.Component { let i = 0; const self = this; for (const key of Object.keys(ids).slice().sort()) { - rows.push( { - if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1); - oldEl = el; - if (el) self.rows.push(el); - }; - })()} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} key={key} keyName={key} />); + rows.push( + { + if (oldEl) self.rows.splice(self.rows.indexOf(oldEl), 1); + oldEl = el; + if (el) self.rows.push(el); + }; + })()} + keyWidth={100 - this.splitPercentage} + rowStyle={'keyValueBox-' + (i++ % 2 ? 'oddRow' : 'evenRow')} + key={key} + keyName={key} + /> + ); } return rows; } @computed get newKeyValue() { - return - { this._keyInput.current!.select(); e.stopPropagation(); }} style={{ width: `${100 - this.splitPercentage}%` }}> - - - { this._valInput.current!.select(); e.stopPropagation(); }} style={{ width: `${this.splitPercentage}%` }}> - - - ; + return ( + + { + this._keyInput.current!.select(); + e.stopPropagation(); + }} + style={{ width: `${100 - this.splitPercentage}%` }}> + + + { + this._valInput.current!.select(); + e.stopPropagation(); + }} + style={{ width: `${this.splitPercentage}%` }}> + + + + ); } @action onDividerMove = (e: PointerEvent): void => { const nativeWidth = this._mainCont.current!.getBoundingClientRect(); - this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)); - } + this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round(((e.clientX - nativeWidth.left) / nativeWidth.width) * 100)); + }; @action onDividerUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onDividerMove); + document.removeEventListener('pointermove', this.onDividerMove); document.removeEventListener('pointerup', this.onDividerUp); - } + }; onDividerDown = (e: React.PointerEvent) => { e.stopPropagation(); e.preventDefault(); - document.addEventListener("pointermove", this.onDividerMove); + document.addEventListener('pointermove', this.onDividerMove); document.addEventListener('pointerup', this.onDividerUp); - } + }; - getTemplate = async () => { - const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template", _chromeHidden: true }); - parent._columnWidth = 100; - for (const row of this.rows.filter(row => row.isChecked)) { - await this.createTemplateField(parent, row); - row.uncheck(); + getFieldView = async () => { + const rows = this.rows.filter(row => row.isChecked); + if (rows.length > 1) { + const parent = Docs.Create.StackingDocument([], { _autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document.data).title}`, _chromeHidden: true }); + for (const row of rows) { + const field = this.createFieldView(DocCast(this.props.Document.data), row); + field && Doc.AddDocToList(parent, 'data', field); + row.uncheck(); + } + return parent; } - return parent; - } + return this.createFieldView(DocCast(this.props.Document.data), rows.lastElement()); + }; - createTemplateField = async (parentStackingDoc: Doc, row: KeyValuePair) => { + createFieldView = (templateDoc: Doc, row: KeyValuePair) => { const metaKey = row.props.keyName; - const sourceDoc = await Cast(this.props.Document.data, Doc); - if (!sourceDoc) { - return; - } + const fieldTemplate = Doc.MakeAlias(templateDoc); + fieldTemplate.title = metaKey; + fieldTemplate.layout = this.inferType(templateDoc[metaKey], metaKey); + return fieldTemplate; + }; - const fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey); - if (!fieldTemplate) { - return; - } - const previousViewType = fieldTemplate._viewType; - Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc)); - previousViewType && (fieldTemplate._viewType = previousViewType); - - Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate); - } - - inferType = async (data: FieldResult, metaKey: string) => { + inferType = (data: FieldResult, metaKey: string) => { const options = { _width: 300, _height: 300, title: metaKey }; - if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") { - return Docs.Create.TextDocument("", options); + if (data instanceof RichTextField || typeof data === 'string' || typeof data === 'number') { + return FormattedTextBox.LayoutString(metaKey); } else if (data instanceof List) { if (data.length === 0) { return Docs.Create.StackingDocument([], options); } - const first = await Cast(data[0], Doc); + const first = DocCast(data[0]); if (!first || !first.data) { return Docs.Create.StackingDocument([], options); } @@ -212,44 +242,52 @@ export class KeyValueBox extends React.Component { return undefined; } } else if (data instanceof ImageField) { - return Docs.Create.ImageDocument("https://image.flaticon.com/icons/png/512/23/23765.png", options); + return ImageBox.LayoutString(metaKey); } - return new Doc; - } + return new Doc(); + }; specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; - const open = cm.findByDescription("Change Perspective..."); - const openItems: ContextMenuProps[] = open && "subitems" in open ? open.subitems : []; + const open = cm.findByDescription('Change Perspective...'); + const openItems: ContextMenuProps[] = open && 'subitems' in open ? open.subitems : []; openItems.push({ - description: "Default Perspective", event: () => { - this.props.addDocTab(this.props.Document, "close"); - this.props.addDocTab(this.fieldDocToLayout, "add:right"); - }, icon: "image" + description: 'Default Perspective', + event: () => { + this.props.addDocTab(this.props.Document, 'close'); + this.props.addDocTab(this.fieldDocToLayout, 'add:right'); + }, + icon: 'image', }); - !open && cm.addItem({ description: "Change Perspective...", subitems: openItems, icon: "external-link-alt" }); - } + !open && cm.addItem({ description: 'Change Perspective...', subitems: openItems, icon: 'external-link-alt' }); + }; render() { - const dividerDragger = this.splitPercentage === 0 ? (null) : -
-
-
; - - return (
- - - - - - - {this.createTable} - {this.newKeyValue} - -
KeyFields
- {dividerDragger} -
); + const dividerDragger = + this.splitPercentage === 0 ? null : ( +
+
+
+ ); + + return ( +
+ + + + + + + {this.createTable} + {this.newKeyValue} + +
+ Key + + Fields +
+ {dividerDragger} +
+ ); } } -- cgit v1.2.3-70-g09d2 From 542a057edbdd4661cfb5ef5d07058aa93b47a113 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 24 Aug 2022 11:16:46 -0400 Subject: cleaned up some of the pin with view code. added a pin button to menu bar when tab is selected to avoid needing to use context menu --- src/client/util/CurrentUserUtils.ts | 5 +- src/client/util/SelectionManager.ts | 3 + src/client/views/collections/CollectionMenu.tsx | 44 --- src/client/views/collections/TabDocView.tsx | 39 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 + src/client/views/nodes/button/FontIconBox.tsx | 96 ++---- .../views/nodes/formattedText/RichTextMenu.tsx | 4 +- src/client/views/nodes/trails/PresBox.tsx | 358 +++++---------------- 8 files changed, 124 insertions(+), 428 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 17d58595c..99a8c895f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -267,7 +267,7 @@ export class CurrentUserUtils { {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, // {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}}, - {key: "Presentation",creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, + {key: "Presentation",creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _chromeHidden: true, boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _backgroundGridShow: true, }}, {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, @@ -675,8 +675,9 @@ export class CurrentUserUtils { CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid, CollectionViewType.NoteTaking]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, + { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_)'}}, { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, - { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement().currentFrame.toString()'}, width: 20, scripts: {}}, + { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}, width: 20, scripts: {}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 1c84af94a..7a555d5f8 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -101,6 +101,9 @@ export namespace SelectionManager { } } ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) { + if (colType === ('tab' as any)) { + return SelectionManager.Views().lastElement()?.props.renderDepth === 0; + } let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true; }); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index eb55650e4..0dc30e0fd 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -586,49 +586,6 @@ export class CollectionViewBaseChrome extends React.Component) => { - if (targetDoc) { - TabDocView.PinDoc(targetDoc); - const presArray: Doc[] = PresBox.Instance?.sortArray(); - const size: number = PresBox.Instance?._selectedArray.size; - const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; - const activeDoc = presSelected ? PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.indexOf(presSelected) + 1] : PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.length - 1]; - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking || targetDoc._viewType === CollectionViewType.NoteTaking) { - const scroll = targetDoc._scrollTop; - activeDoc.presPinView = true; - activeDoc.presPinViewScroll = scroll; - } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.MAP) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeDoc.presPinView = true; - activeDoc.presPinViewX = x; - activeDoc.presPinViewY = y; - activeDoc.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.VID) { - activeDoc.presPinView = true; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeDoc.presPinClipWidth = width; - activeDoc.presPinView = true; - } - } - }; - - @computed - get pinWithViewButton() { - const presPinWithViewIcon = ; - return !this.selectedDoc ? null : ( - {'Pin with current view'}
} placement="top"> - - - ); - } - @undoBatch onAlias = () => { if (this.selectedDoc && this.selectedDocumentView) { @@ -722,7 +679,6 @@ export class CollectionViewBaseChrome extends React.Component
{this.subChrome}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index e147f34d2..73574bdb3 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -242,43 +242,8 @@ export class TabDocView extends React.Component { const size: number = PresBox.Instance?._selectedArray.size; const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); - // If pinWithView option set then update scale and x / y props of slide - if (pinProps?.pinWithView) { - const viewProps = pinProps.pinWithView; - pinDoc.presPinView = true; - pinDoc.presPinViewX = viewProps.bounds.left + viewProps.bounds.width / 2; - pinDoc.presPinViewY = viewProps.bounds.top + viewProps.bounds.height / 2; - pinDoc.presPinViewScale = viewProps.scale; - pinDoc.contentBounds = new List([viewProps.bounds.left, viewProps.bounds.top, viewProps.bounds.left + viewProps.bounds.width, viewProps.bounds.top + viewProps.bounds.height]); - } - if (pinProps?.pinDocView) { - const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(pinDoc.type as any) || pinDoc._viewType === CollectionViewType.Stacking; - const pannable: boolean = (pinDoc.type === DocumentType.COL && doc._viewType === CollectionViewType.Freeform) || doc.type === DocumentType.IMG; - if (scrollable) { - const scroll = doc._scrollTop; - pinDoc.presPinView = true; - pinDoc.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(doc.type as any)) { - pinDoc.presPinView = true; - pinDoc.presStartTime = doc._currentTimecode; - pinDoc.presEndTime = NumCast(doc._currentTimecode) + 0.1; - } else if (pannable) { - pinDoc.presPinView = true; - pinDoc.presPinViewX = pinDoc._panX; - pinDoc.presPinViewY = pinDoc._panY; - pinDoc.presPinViewScale = pinDoc._viewScale; - const pw = NumCast(pinProps.panelWidth); - const ph = NumCast(pinProps.panelHeight); - const ps = NumCast(pinDoc._viewScale); - if (pw && ph && ps) { - pinDoc.contentBounds = new List([NumCast(pinDoc.panX) - pw / 2 / ps, NumCast(pinDoc.panY) - ph / 2 / ps, NumCast(pinDoc.panX) + pw / 2 / ps, NumCast(pinDoc.panY) + ph / 2 / ps]); - } - } else if (doc.type === DocumentType.COMPARISON) { - const width = doc._clipWidth; - pinDoc.presPinClipWidth = width; - pinDoc.presPinView = true; - } - } + + PresBox.pinDocView(pinDoc, pinProps); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); if (!pinProps?.audioRange && duration !== undefined) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 03beaf65e..052cbd3bb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -2213,3 +2213,6 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); +ScriptingGlobals.add(function pinWithView(readOnly: boolean) { + !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocView: true, panelWidth: view.props.PanelWidth(), panelHeight: view.props.PanelHeight() })); +}); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index fc5bf86f4..c72b5ca9b 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -254,16 +254,13 @@ export class FontIconBox extends DocComponent() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const script = ScriptCast(this.rootDoc.script); - if (!script) { - return null; - } let noviceList: string[] = []; let text: string | undefined; let dropdown = true; let icon: IconProp = 'caret-down'; try { - if (script.script.originalScript.startsWith('setView')) { + if (script?.script.originalScript.startsWith('setView')) { const selected = SelectionManager.Docs().lastElement(); if (selected) { if (StrCast(selected.type) === DocumentType.COL) { @@ -279,7 +276,7 @@ export class FontIconBox extends DocComponent() { text = 'User Default'; } noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; - } else if (script.script.originalScript.startsWith('setFont')) { + } else if (script?.script.originalScript.startsWith('setFont')) { 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']; @@ -289,11 +286,9 @@ export class FontIconBox extends DocComponent() { } // Get items to place into the list - const list = this.buttonList.map(value => { - if (Doc.noviceMode && !noviceList.includes(value)) { - return; - } - return ( + const list = this.buttonList + .filter(value => !Doc.noviceMode || noviceList.includes(value)) + .map(value => (
() { onClick={() => script.script.run({ value }).result}> {value[0].toUpperCase() + value.slice(1)}
- ); - }); + )); const label = !this.label || !FontIconBox.GetShowLabels() ? null : ( @@ -451,7 +445,6 @@ export class FontIconBox extends DocComponent() { @computed get defaultButton() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const active: string = StrCast(this.rootDoc.dropDownOpen); return (
@@ -487,89 +480,50 @@ export class FontIconBox extends DocComponent() { render() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const label = - !this.label || !FontIconBox.GetShowLabels() ? null : ( -
- {this.label} -
- ); - - const menuLabel = + const label = (noBackground: boolean = false) => !this.label || !FontIconBox.GetShowLabels() ? null : ( -
+
{this.label}
); - // TODO:glr Add label of button type - let button: JSX.Element | null = this.defaultButton; + let button: JSX.Element = this.defaultButton; + // prettier-ignore switch (this.type) { - case ButtonType.TextButton: - button = ( + case ButtonType.DropdownList: return this.dropdownListButton; + case ButtonType.ColorButton: return this.colorButton; + case ButtonType.NumberButton: return this.numberButton; + case ButtonType.EditableText: return this.editableText; + case ButtonType.DropdownButton: button = this.dropdownButton; break; + case ButtonType.ToggleButton: button = this.toggleButton; break; + case ButtonType.TextButton: button = (
{this.Icon(color)} {StrCast(this.rootDoc.buttonText) ?
{StrCast(this.rootDoc.buttonText)}
: null} - {label} -
- ); - // button = - break; - case ButtonType.EditableText: - button = this.editableText; - break; - case ButtonType.NumberButton: - button = this.numberButton; - break; - case ButtonType.DropdownButton: - button = this.dropdownButton; - break; - case ButtonType.DropdownList: - button = this.dropdownListButton; - break; - case ButtonType.ColorButton: - button = this.colorButton; - break; - case ButtonType.ToolButton: - button = ( -
- {this.Icon(color)} - {label} + {label()}
); break; - case ButtonType.ToggleButton: - button = this.toggleButton; - // button = - break; case ButtonType.ClickButton: - button = ( -
+ case ButtonType.ToolButton: button = ( +
{this.Icon(color)} - {label} + {label()}
); break; - case ButtonType.MenuButton: - button = ( + case ButtonType.MenuButton: button = (
{this.Icon(color)} - {menuLabel} + {label(true)}
); break; - default: - break; } - const retval = - !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? ( - button - ) : button !== null ? ( - {StrCast(this.layoutDoc.toolTip)}
}>{button} - ) : null; - return retval; + return !this.layoutDoc.toolTip ? button : {StrCast(this.layoutDoc.toolTip)}
}>{button}; } } @@ -698,7 +652,7 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - return RichTextMenu.Instance.fontSize.replace('px', ''); + return RichTextMenu.Instance?.fontSize.replace('px', ''); } if (typeof size === 'number') size = size.toString(); if (size && Number(size).toString() === size) size += 'px'; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 2a77210ae..0cbe60c0c 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -16,7 +16,7 @@ import { SelectionManager } from '../../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { FieldViewProps } from '../FieldView'; -import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox'; +import { FormattedTextBox } from './FormattedTextBox'; import { updateBullets } from './ProsemirrorExampleTransfer'; import './RichTextMenu.scss'; import { schema } from './schema_rts'; @@ -29,7 +29,7 @@ export class RichTextMenu extends AntimodeMenu { private _linkToRef = React.createRef(); @observable public view?: EditorView; - public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined; + public editorProps: FieldViewProps | undefined; public _brushMap: Map> = new Map(); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 05e09361b..eb40089ec 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -358,20 +358,24 @@ export class PresBox extends ViewBoxBaseComponent() { if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); }; + static pinDataTypes(target: Doc) { + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking; + const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); + const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any); + const clippable = [DocumentType.COMPARISON].includes(target.type as any); + return { scrollable, pannable, temporal, clippable }; + } // navigates to the bestTarget document by making sure it is on screen, // then it applies the view specs stored in activeItem to @action static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) { - if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { - bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - bestTarget._scrollTop = activeItem.presPinViewScroll; - } else if (bestTarget.type === DocumentType.COMPARISON) { - bestTarget._clipWidth = activeItem.presPinClipWidth; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) { - bestTarget._currentTimecode = activeItem.presStartTime; - } else { + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; + const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(bestTarget); + if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; + if (temporal) bestTarget._currentTimecode = activeItem.presStartTime; + if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll; + if (pannable) { const contentBounds = Cast(activeItem.contentBounds, listSpec('number')); - bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; if (contentBounds) { bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2; bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2; @@ -388,6 +392,43 @@ export class PresBox extends ViewBoxBaseComponent() { return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); } + /// copies values from the targetDoc (which is the prototype of the pinDoc) to + /// reserved fields on the pinDoc so that those values can be restored to the + /// target doc when navigating to it. + @action + static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined) { + if (pinProps?.pinWithView) { + // If pinWithView option set then update scale and x / y props of slide + const bounds = pinProps.pinWithView.bounds; + pinDoc.presPinView = true; + pinDoc.presPinViewX = bounds.left + bounds.width / 2; + pinDoc.presPinViewY = bounds.top + bounds.height / 2; + pinDoc.presPinViewScale = pinProps.pinWithView.scale; + pinDoc.contentBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); + } + if (pinProps?.pinDocView) { + const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(pinDoc); + pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable; + + if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; + else if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; + else if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1; + else if (pannable) { + const panX = NumCast(pinDoc._panX); + const panY = NumCast(pinDoc._panY); + const pw = NumCast(pinProps.panelWidth); + const ph = NumCast(pinProps.panelHeight); + const ps = NumCast(pinDoc._viewScale); + if (pw && ph && ps) { + pinDoc.contentBounds = new List([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]); + } + pinDoc.presPinViewX = panX; + pinDoc.presPinViewY = panY; + pinDoc.presPinViewScale = ps; + } + } + } + /** * This method makes sure that cursor navigates to the element that * has the option open and last in the group. @@ -1540,26 +1581,15 @@ export class PresBox extends ViewBoxBaseComponent() { } } - @computed get effectDirection(): string { - let effect = ''; + @computed get effectDirection() { + // prettier-ignore switch (this.activeItem.presEffectDirection) { - case 'left': - effect = 'Enter from left'; - break; - case 'right': - effect = 'Enter from right'; - break; - case 'top': - effect = 'Enter from top'; - break; - case 'bottom': - effect = 'Enter from bottom'; - break; - default: - effect = 'Enter from center'; - break; + case 'left': return 'Enter from left'; + case 'right': return 'Enter from right'; + case 'top': return'Enter from top'; + case 'bottom': return 'Enter from bottom'; } - return effect; + return 'Enter from center'; } @undoBatch @@ -1582,179 +1612,6 @@ export class PresBox extends ViewBoxBaseComponent() { }); }; - @computed get presPinViewOptionsDropdown() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - const presPinWithViewIcon = ; - return ( - <> - {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null} -
- -
{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}
- - }> -
{ - activeItem.presPinView = !activeItem.presPinView; - targetDoc.presPinView = activeItem.presPinView; - if (activeItem.presPinView) { - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { - const scroll = targetDoc._scrollTop; - activeItem.presPinView = true; - activeItem.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeItem.presStartTime = targetDoc._currentTimecode; - activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinView = true; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeItem.presPinClipWidth = width; - activeItem.presPinView = true; - } - } - }}> - {presPinWithViewIcon} -
-
- {activeItem.presPinView ? ( - -
{'Update the pinned view with the view of the selected document'}
- - }> -
{ - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { - const scroll = targetDoc._scrollTop; - activeItem.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeItem.presStartTime = targetDoc._currentTimecode; - activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const clipWidth = targetDoc._clipWidth; - activeItem.presPinClipWidth = clipWidth; - } else { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } - }}> - Update -
-
- ) : null} -
- - ); - } - - @computed get panOptionsDropdown() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - return ( - <> - {this.panable ? ( -
-
-
Pan X
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { - const val = e.target.value; - activeItem.presPinViewX = Number(val); - })} - /> -
-
-
-
Pan Y
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { - const val = e.target.value; - activeItem.presPinViewY = Number(val); - })} - /> -
-
-
-
Scale
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { - const val = e.target.value; - activeItem.presPinViewScale = Number(val); - })} - /> -
-
-
- ) : null} - - ); - } - - @computed get scrollOptionsDropdown() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - return ( - <> - {this.scrollable ? ( -
-
-
Scroll
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { - const val = e.target.value; - activeItem.presPinViewScroll = Number(val); - })} - /> -
-
-
- ) : null} - - ); - } - @computed get mediaStopSlides() { const activeItem: Doc = this.activeItem; const list = this.childDocs.map((doc, i) => { @@ -2102,42 +1959,23 @@ export class PresBox extends ViewBoxBaseComponent() { }; createTemplate = (layout: string, input?: string) => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - let x = 0; - let y = 0; - if (activeItem && targetDoc) { - x = NumCast(targetDoc.x); - y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20; - } - let doc = undefined; - const title = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' }); - const subtitle = Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' }); - const header = Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' }); - const contentTitle = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' }); - const content = Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' }); - const content1 = Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' }); - const content2 = Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' }); + const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0; + const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0; + const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' }); + const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' }); + const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' }); + const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' }); + const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' }); + const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' }); + const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' }); + // prettier-ignore switch (layout) { - case 'blank': - doc = Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x: x, y: y }); - break; - case 'title': - doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); - break; - case 'header': - doc = Docs.Create.FreeformDocument([header], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); - break; - case 'content': - doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); - break; - case 'twoColumns': - doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); - break; - default: - break; + case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y }); + case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x, y }); + case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x, y }); + case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x, y }); + case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x, y }) } - return doc; }; // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @@ -2201,42 +2039,22 @@ export class PresBox extends ViewBoxBaseComponent() { /** * Returns the collection type as a string for headers */ - @computed get stringType(): string { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - let type: string = ''; - if (activeItem) { - switch (targetDoc.type) { - case DocumentType.PDF: - type = 'PDF'; - break; - case DocumentType.RTF: - type = 'Text node'; - break; - case DocumentType.COL: - type = 'Collection'; - break; - case DocumentType.AUDIO: - type = 'Audio'; - break; - case DocumentType.VID: - type = 'Video'; - break; - case DocumentType.IMG: - type = 'Image'; - break; - case DocumentType.WEB: - type = 'Web page'; - break; - case DocumentType.MAP: - type = 'Map'; - break; - default: - type = 'Other node'; - break; + @computed get stringType() { + if (this.activeItem) { + // prettier-ignore + switch (this.targetDoc.type) { + case DocumentType.PDF: return 'PDF'; + case DocumentType.RTF: return 'Text node'; + case DocumentType.COL: return 'Collection'; + case DocumentType.AUDIO: return 'Audio'; + case DocumentType.VID: return 'Video'; + case DocumentType.IMG: return 'Image'; + case DocumentType.WEB: return 'Web page'; + case DocumentType.MAP: return 'Map'; + default: return 'Other node'; } } - return type; + return ''; } @observable private openActiveColorPicker: boolean = false; @@ -2845,10 +2663,6 @@ export class PresBox extends ViewBoxBaseComponent() { case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB: this.updateList(activeItem.frameList); break; - case DocumentType.COL: - break; - default: - break; } }; -- cgit v1.2.3-70-g09d2 From 94dbcc40067cb6637f7a535ff305d9452a3f40d1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 24 Aug 2022 14:07:41 -0400 Subject: made text boxes support animation frames. fixed dragging on unselected pres element ttitles. cleaned up setting ink to have animation frames. --- src/client/documents/Documents.ts | 3 ++- src/client/views/InkStrokeProperties.ts | 10 +--------- src/client/views/InkingStroke.tsx | 11 ----------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 19 ++++++++++++++++--- src/client/views/nodes/trails/PresElementBox.tsx | 8 +++++++- src/fields/ScriptField.ts | 12 +++++++++++- 6 files changed, 37 insertions(+), 26 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e579bfd8a..7c50f21a5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1716,7 +1716,8 @@ export namespace DocUtils { .replace(/\.[a-z0-9]*$/, ''); if (Upload.isImageInformation(result)) { const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); - proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (StrCast((result.exifData?.data as any)?.Orientation).includes('Rotate 90') ? 5 : undefined); + const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase(); + proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); proto['data-nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; proto['data-nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); if (NumCast(proto['data-nativeOrientation']) >= 5) { diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 1f5f16592..d19a916f9 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -4,7 +4,6 @@ import { Doc, NumListCast, Opt } from '../../fields/Doc'; import { InkData, InkField, InkTool, PointData } from '../../fields/InkField'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; -import { ComputedField } from '../../fields/ScriptField'; import { Cast, NumCast } from '../../fields/Types'; import { Point } from '../../pen-gestures/ndollar'; import { DocumentType } from '../documents/DocumentTypes'; @@ -12,7 +11,6 @@ import { FitOneCurve } from '../util/bezierFit'; import { DocumentManager } from '../util/DocumentManager'; import { undoBatch } from '../util/UndoManager'; import { InkingStroke } from './InkingStroke'; -import { CollectionFreeFormDocumentView } from './nodes/CollectionFreeFormDocumentView'; import { DocumentView } from './nodes/DocumentView'; export class InkStrokeProperties { @@ -67,13 +65,7 @@ export class InkStrokeProperties { 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; - if (doc.activeFrame !== undefined) { - const findexed = Cast(doc[`data-indexed`], listSpec(InkField), []).slice(); - findexed[NumCast(doc.activeFrame)] = new InkField(newPoints); - doc[`data-indexed`] = new List(findexed); - } else { - Doc.GetProto(doc).data = new InkField(newPoints); - } + Doc.GetProto(doc).data = new InkField(newPoints); appliedFunc = true; } } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index ceaabd0e1..dae1c10bb 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -68,17 +68,6 @@ export class InkingStroke extends ViewBoxBaseComponent() { componentDidMount() { this.props.setContentView?.(this); - this._disposers.activeFrame = reaction( - () => this.rootDoc.activeFrame !== undefined && !(ComputedField.WithoutComputed(() => FieldValue(this.rootDoc[this.fieldKey])) instanceof ComputedField), - () => { - const newPoints = Cast(this.rootDoc[this.fieldKey], InkField, null).inkData; - this.rootDoc[this.fieldKey] = ComputedField.MakeInterpolated(this.fieldKey, 'activeFrame', this.rootDoc, NumCast(this.rootDoc.activeFrame)); - const findexed = Cast(this.rootDoc[`data-indexed`], listSpec(InkField), []).slice(); - findexed[NumCast(this.rootDoc.activeFrame)] = new InkField(newPoints); - this.rootDoc[this.fieldKey + '-indexed'] = new List(findexed); - }, - { fireImmediately: true } - ); this._disposers.selfDisper = reaction( () => this.props.isSelected(), // react to stroke being deselected by turning off ink handles selected => !selected && (InkStrokeProperties.Instance._controlButton = false) diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 45f68e0f0..86566ac6a 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -16,6 +16,11 @@ import { StyleProp } from '../StyleProvider'; import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps } from './DocumentView'; import React = require('react'); +import { InkField } from '../../../fields/InkField'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { Field } from '../../util/ProsemirrorCopy/prompt'; +import { RefField } from '../../../fields/RefField'; +import { ObjectField } from '../../../fields/ObjectField'; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; @@ -33,6 +38,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { export class CollectionFreeFormDocumentView extends DocComponent() { public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames public static animStringFields = ['backgroundColor', 'color']; // fields that are configured to be animatable using animation frames + public static animDataFields = ['data', 'text']; // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; get displayName() { @@ -118,6 +124,10 @@ export class CollectionFreeFormDocumentView extends DocComponent { + const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any); + }); }) ); setTimeout( @@ -164,10 +174,13 @@ export class CollectionFreeFormDocumentView extends DocComponent(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1))); } - CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolated(val, 'activeFrame', doc, currTimecode))); + CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedNumber(val, 'activeFrame', doc, currTimecode))); CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode))); - doc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0'); - doc.dataTransition = 'inherit'; + CollectionFreeFormDocumentView.animDataFields.forEach(val => (Doc.GetProto(doc)[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', Doc.GetProto(doc), currTimecode))); + const targetDoc = doc.type === DocumentType.RTF ? Doc.GetProto(doc) : doc; // data fields, like rtf 'text' exist on the data doc, so + doc !== targetDoc && (targetDoc.context = doc.context); // the computed fields don't see the layout doc -- need to copy the context to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!) + targetDoc.activeFrame = ComputedField.MakeFunction('self.context?._currentFrame||0'); + targetDoc.dataTransition = 'inherit'; }); } diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 3af8cad9a..7888d0841 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -501,7 +501,13 @@ export class PresElementBox extends ViewBoxBaseComponent() { backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isSelected ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined, }}> -
+
{`${this.indexInPres + 1}. `}
StrCast(activeItem.title)} SetValue={this.onSetValue} />
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 0fd992d3b..48d5c5563 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -189,7 +189,7 @@ export class ComputedField extends ScriptField { const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); return compiled.compiled ? new ComputedField(compiled) : undefined; } - public static MakeInterpolated(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) { + public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) { if (!doc[`${fieldKey}-indexed`]) { const flist = new List(numberRange(curTimecode + 1).map(i => undefined) as any as number[]); flist[curTimecode] = NumCast(doc[fieldKey]); @@ -209,6 +209,16 @@ export class ComputedField extends ScriptField { const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {}); return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; } + public static MakeInterpolatedDataField(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) { + if (!doc[`${fieldKey}-indexed`]) { + const flist = new List(numberRange(curTimecode + 1).map(i => undefined) as any as Field[]); + flist[curTimecode] = Field.Copy(doc[fieldKey]); + doc[`${fieldKey}-indexed`] = flist; + } + const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {}); + const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {}); + return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; + } } export namespace ComputedField { let useComputed = true; -- cgit v1.2.3-70-g09d2 From 0ed7131587c6739483da64a93d9f2ab6fdfbc15a Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 25 Aug 2022 11:06:38 -0400 Subject: fixed crashes in notetaking view and cleaned up code a bit. fixed undo of column deletion. --- src/client/util/CurrentUserUtils.ts | 11 ++- .../views/collections/CollectionNoteTakingView.tsx | 103 ++++++++------------- .../collections/CollectionNoteTakingViewColumn.tsx | 12 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 ++-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 10 +- src/client/views/nodes/DocumentView.scss | 3 + src/fields/SchemaHeaderField.ts | 2 +- src/fields/ScriptField.ts | 10 +- 8 files changed, 79 insertions(+), 90 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 99a8c895f..2321d18ee 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -597,14 +597,17 @@ export class CurrentUserUtils { /// initializes the required buttons in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc, field="myDockedBtns") { const dockedBtns = DocCast(doc[field]); - const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}) => + const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}, funcs?: {[key:string]:string}) => DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? - CurrentUserUtils.createToolButton(opts), scripts); + CurrentUserUtils.createToolButton(opts), scripts, funcs); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }}, - { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }} - ]; + { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }}, + // { scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, opts: { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, width: 20}}, + // { scripts: { onClick:""}, opts: { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, width: 20}, funcs: { buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}}, + // { scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, opts:{title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, width: 20,} }, + ]; const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts = { title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index b359ef420..5a6d899ef 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -11,7 +11,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; -import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -19,7 +19,6 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { LightboxView } from '../LightboxView'; -import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; @@ -30,13 +29,6 @@ import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivid import { CollectionSubView } from './CollectionSubView'; const _global = (window /* browser */ || global) /* node */ as any; -export type collectionNoteTakingViewProps = { - chromeHidden?: boolean; - viewType?: CollectionViewType; - NativeWidth?: () => number; - NativeHeight?: () => number; -}; - /** * CollectionNoteTakingView is a column-based view for displaying documents. In this view, the user can (1) * add and remove columns (2) change column sizes and (3) move documents within and between columns. This @@ -45,28 +37,32 @@ export type collectionNoteTakingViewProps = { * the rest of Dash, so it may be worthwhile to transition the headers to simple documents. */ @observer -export class CollectionNoteTakingView extends CollectionSubView>() { +export class CollectionNoteTakingView extends CollectionSubView() { _disposers: { [key: string]: IReactionDisposer } = {}; _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef(); + notetakingCategoryField = 'NotetakingCategory'; + dividerWidth = 16; @observable docsDraggedRowCol: number[] = []; @observable _cursor: CursorProperty = 'grab'; @observable _scroll = 0; @computed get chromeHidden() { - return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); + return BoolCast(this.layoutDoc.chromeHidden); } // columnHeaders returns the list of SchemaHeaderFields currently being used by the layout doc to render the columns @computed get columnHeaders() { const columnHeaders = Cast(this.dataDoc.columnHeaders, listSpec(SchemaHeaderField), null); - const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders.find(sh => sh.heading === 'unset')); + const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset')); if (needsUnsetCategory) { - setTimeout(() => columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1))); + setTimeout(() => { + const needsUnsetCategory = this.childDocs.some(d => !d[this.notetakingCategoryField] && !columnHeaders?.find(sh => sh.heading === 'unset')); + if (needsUnsetCategory) { + if (columnHeaders) columnHeaders.push(new SchemaHeaderField('unset', undefined, undefined, 1)); + else this.dataDoc.columnHeaders = new List(); + } + }); } - return columnHeaders; - } - // notetakingCategoryField returns the key to accessing a document's column value - @computed get notetakingCategoryField() { - return 'NotetakingCategory'; + return columnHeaders ?? ([] as SchemaHeaderField[]); } @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); @@ -74,10 +70,6 @@ export class CollectionNoteTakingView extends CollectionSubView([new SchemaHeaderField('New Column', undefined, undefined, 1)]); - } - } - // children is passed as a prop to the NoteTakingField, which uses this function // to render the docs you see within an individual column. children = (docs: Doc[]) => { @@ -149,7 +132,7 @@ export class CollectionNoteTakingView extends CollectionSubView rowCol[1]) { const offset = 0; sections.get(columnHeaders[rowCol[1]])?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged); } @@ -236,9 +219,6 @@ export class CollectionNoteTakingView extends CollectionSubView (d[this.notetakingCategoryField] = colHeader)); // used to notify sections to re-render this.docsDraggedRowCol.length = 0; - this.docsDraggedRowCol.push(dropInd, this.getColumnFromXCoord(xCoord)); + const columnFromCoord = this.getColumnFromXCoord(xCoord); + columnFromCoord !== undefined && this.docsDraggedRowCol.push(dropInd, columnFromCoord); } }; // getColumnFromXCoord returns the column index for a given x-coordinate (currently always the client's mouse coordinate). // This function is used to know which document a column SHOULD be in while it is being dragged. - getColumnFromXCoord = (xCoord: number): number => { + getColumnFromXCoord = (xCoord: number): number | undefined => { + let colIndex: number | undefined = undefined; const numColumns = this.columnHeaders.length; const coords = []; let colStartXCoord = 0; @@ -411,7 +393,6 @@ export class CollectionNoteTakingView extends CollectionSubView coords[i] && xCoord < coords[i + 1]) { colIndex = i; @@ -423,20 +404,16 @@ export class CollectionNoteTakingView extends CollectionSubView { - const colIndex = this.getColumnFromXCoord(xCoord); - const colHeader = StrCast(this.columnHeaders[colIndex].heading); - // const docs = this.childDocList - const docs = this.childDocs; const docsMatchingHeader: Doc[] = []; - if (docs) { - docs.map(d => { - if (d instanceof Promise) return; - const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset'; - if (sectionValue.toString() == colHeader) { - docsMatchingHeader.push(d); - } - }); - } + const colIndex = this.getColumnFromXCoord(xCoord); + const colHeader = colIndex === undefined ? 'unset' : StrCast(this.columnHeaders[colIndex].heading); + this.childDocs?.map(d => { + if (d instanceof Promise) return; + const sectionValue = (d[this.notetakingCategoryField] as object) ?? 'unset'; + if (sectionValue.toString() == colHeader) { + docsMatchingHeader.push(d); + } + }); return docsMatchingHeader; }; @@ -511,7 +488,7 @@ export class CollectionNoteTakingView extends CollectionSubView this.addDocument(doc)); const newDoc = this.childDocs.lastElement(); - const colHeader = StrCast(this.columnHeaders[colInd].heading); + const colHeader = colInd === undefined ? 'unset' : StrCast(this.columnHeaders[colInd].heading); newDoc[this.notetakingCategoryField] = colHeader; const docs = this.childDocList; if (docs && targInd !== -1) { @@ -570,9 +547,9 @@ export class CollectionNoteTakingView extends CollectionSubView { - for (const header of this.columnHeaders) { - if (header.heading == value) { - alert('You cannot use an existing column name. Please try a new column name'); - return value; + if (this.columnHeaders) { + for (const header of this.columnHeaders) { + if (header.heading == value) { + alert('You cannot use an existing column name. Please try a new column name'); + return value; + } } } const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null); @@ -678,10 +657,10 @@ export class CollectionNoteTakingView extends CollectionSubView { - const columnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null); - if (columnHeaders && this.props.headingObject) { - const index = columnHeaders.indexOf(this.props.headingObject); + const acolumnHeaders = Cast(this.props.Document.columnHeaders, listSpec(SchemaHeaderField), null); + if (acolumnHeaders && this.props.headingObject) { + const index = acolumnHeaders.indexOf(this.props.headingObject); + const columnHeaders = new List(acolumnHeaders.map(header => header[Copy]())); // needed for undo to work properly. otherwise we end up changing field values in the undo stack since they are shared by reference const newColIndex = index > 0 ? index - 1 : 1; const newColHeader = this.props.columnHeaders ? this.props.columnHeaders[newColIndex] : undefined; const newHeading = newColHeader ? newColHeader.heading : 'unset'; this.props.docList.forEach(d => (d[this.props.pivotField] = newHeading)); const colWidth = this.props.columnHeaders ? this.props.columnHeaders[index].width : 0; columnHeaders.splice(index, 1); + Doc.GetProto(this.props.Document).columnHeaders = columnHeaders; this.props.resizeColumns(false, colWidth, index); } }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 052cbd3bb..c44b33ed0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -44,7 +44,6 @@ import { PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; -import { CollectionDockingView } from '../CollectionDockingView'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { TabDocView } from '../TabDocView'; @@ -233,11 +232,11 @@ export class CollectionFreeFormView extends CollectionSubView newBox[field]); - CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field}-indexed`]); - CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field]); + const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field.key]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field.key}-indexed`]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field.key]); delete newBox.activeFrame; - CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== 'opacity' && (newBox[field] = vals[i])); + CollectionFreeFormDocumentView.animFields.forEach((field, i) => field.key !== 'opacity' && (newBox[field.key] = vals[i])); } } if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) { @@ -275,10 +274,11 @@ export class CollectionFreeFormView extends CollectionSubView() { - public static animFields = ['_height', '_width', 'x', 'y', '_scrollTop', 'opacity']; // fields that are configured to be animatable using animation frames + public static animFields: { key: string; val?: number }[] = [{ key: '_height' }, { key: '_width' }, { key: 'x' }, { key: 'y' }, { key: '_scrollTop' }, { key: 'opacity', val: 1 }, { key: 'viewScale', val: 1 }, { key: 'panX' }, { key: 'panY' }]; // fields that are configured to be animatable using animation frames public static animStringFields = ['backgroundColor', 'color']; // fields that are configured to be animatable using animation frames public static animDataFields = ['data', 'text']; // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @@ -88,9 +88,9 @@ export class CollectionFreeFormDocumentView extends DocComponent { - p[val] = Cast(`${val}-indexed`, listSpec('number'), [NumCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number); + p[val.key] = Cast(`${val}-indexed`, listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number); return p; }, {} as { [val: string]: Opt }); } @@ -117,7 +117,7 @@ export class CollectionFreeFormDocumentView extends DocComponent { doc._viewTransition = doc.dataTransition = 'all 1s'; CollectionFreeFormDocumentView.animFields.forEach(val => { - const findexed = Cast(doc[`${val}-indexed`], listSpec('number'), null); + const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null); findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); }); CollectionFreeFormDocumentView.animStringFields.forEach(val => { @@ -174,7 +174,7 @@ export class CollectionFreeFormDocumentView extends DocComponent(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1))); } - CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedNumber(val, 'activeFrame', doc, currTimecode))); + CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val))); CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode))); CollectionFreeFormDocumentView.animDataFields.forEach(val => (Doc.GetProto(doc)[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', Doc.GetProto(doc), currTimecode))); const targetDoc = doc.type === DocumentType.RTF ? Doc.GetProto(doc) : doc; // data fields, like rtf 'text' exist on the data doc, so diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 9aaaf1e68..ab7116150 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -2,6 +2,7 @@ .documentView-effectsWrapper { border-radius: inherit; + transition: inherit; } // documentViews have a docView-hack tag which is replaced by this tag when capturing bitmaps (when the dom is converted to an html string) @@ -212,10 +213,12 @@ display: flex; width: 100%; height: 100%; + transition: inherit; .contentFittingDocumentView-previewDoc { position: relative; display: inline; + transition: inherit; } .contentFittingDocumentView-input { diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts index 1321bc327..0b51db70b 100644 --- a/src/fields/SchemaHeaderField.ts +++ b/src/fields/SchemaHeaderField.ts @@ -115,7 +115,7 @@ export class SchemaHeaderField extends ObjectField { } [ToScriptString]() { - return `header(${this.heading},${this.type}})`; + return `header(${this.heading},${this.type},${this.width}})`; } [ToString]() { return `SchemaHeaderField`; diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 48d5c5563..d38a019b3 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -189,13 +189,13 @@ export class ComputedField extends ScriptField { const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); return compiled.compiled ? new ComputedField(compiled) : undefined; } - public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number) { + public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt) { if (!doc[`${fieldKey}-indexed`]) { const flist = new List(numberRange(curTimecode + 1).map(i => undefined) as any as number[]); - flist[curTimecode] = NumCast(doc[fieldKey]); + flist[curTimecode] = Cast(doc[fieldKey], 'number', null); doc[`${fieldKey}-indexed`] = flist; } - const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {}); + const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, ${defaultVal})`, {}, true, {}); const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {}); return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined; } @@ -260,8 +260,8 @@ ScriptingGlobals.add( ); ScriptingGlobals.add( - function getIndexVal(list: any[], index: number) { - return list?.reduce((p, x, i) => ((i <= index && x !== undefined) || p === undefined ? x : p), undefined as any); + function getIndexVal(list: any[], index: number, defaultVal: Opt = undefined) { + return list?.reduce((p, x, i) => ((i <= index && x !== undefined) || p === undefined ? x : p), defaultVal); }, 'returns the value at a given index of a list', '(list: any[], index: number)' -- cgit v1.2.3-70-g09d2 From 9263422913f30b54922f3c0d7290e36d4a509455 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Aug 2022 12:43:00 -0400 Subject: extending pres trails to allow ink animation without using keyframes. --- src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionMenu.tsx | 20 +++---- src/client/views/collections/TabDocView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - .../views/nodes/CollectionFreeFormDocumentView.tsx | 10 ++-- src/client/views/nodes/trails/PresBox.tsx | 67 +++++++++++++++------- 6 files changed, 61 insertions(+), 41 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 06be4d194..515faa316 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -154,10 +154,10 @@ export class MainView extends React.Component { if (!MainView.Live) { DocServer.setPlaygroundFields([ 'dataTransition', + 'viewTransition', 'treeViewOpen', 'showSidebar', 'sidebarWidthPercent', - 'viewTransition', 'panX', 'panY', 'fitWidth', diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 0dc30e0fd..6a0f69359 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -726,18 +726,16 @@ export class CollectionFreeFormViewChrome extends React.Component { const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); - PresBox.pinDocView(pinDoc, pinProps); + PresBox.pinDocView(pinDoc, pinProps, doc); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); if (!pinProps?.audioRange && duration !== undefined) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c44b33ed0..210370d39 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -278,7 +278,6 @@ export class CollectionFreeFormView extends CollectionSubView { - p[val.key] = Cast(`${val}-indexed`, listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number); + p[val.key] = Cast(doc[`${val.key}-indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number); return p; }, {} as { [val: string]: Opt }); } public static getStringValues(doc: Doc, time: number) { return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => { - p[val] = Cast(`${val}-indexed`, listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string); + p[val] = Cast(doc[`${val}-indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string); return p; }, {} as { [val: string]: Opt }); } @@ -140,8 +140,8 @@ export class CollectionFreeFormDocumentView extends DocComponent (doc._viewTransition = doc.dataTransition = 'all 1s')); + public static gotoKeyframe(docs: Doc[], duration = 1000) { + docs.forEach(doc => (doc._viewTransition = doc.dataTransition = `all ${duration}ms`)); setTimeout( () => docs.forEach(doc => { @@ -226,7 +226,6 @@ export class CollectionFreeFormDocumentView extends DocComponent {this.props.renderCutoffProvider(this.props.Document) ? ( diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index eb40089ec..f254eaba6 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -6,7 +6,7 @@ import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc'; -import { InkTool } from '../../../../fields/InkField'; +import { InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; @@ -19,8 +19,7 @@ import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; -import { MarqueeViewBounds } from '../../collections/collectionFreeForm'; -import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu'; +import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm'; import { CollectionView } from '../../collections/CollectionView'; import { TabDocView } from '../../collections/TabDocView'; import { ViewBoxBaseComponent } from '../../DocComponent'; @@ -30,6 +29,8 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie import { FieldView, FieldViewProps } from '../FieldView'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +import { Copy } from '../../../../fields/FieldSymbols'; +import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu'; export interface PinProps { audioRange?: boolean; @@ -317,8 +318,15 @@ export class PresBox extends ViewBoxBaseComponent() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (activeItem.presActiveFrame !== undefined) { + const transTime = NumCast(activeItem.presDuration, 500); const context = DocCast(DocCast(activeItem.presentationTargetDoc).context); - context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(activeItem.presActiveFrame)); + if (context) { + const contextView = DocumentManager.Instance.getFirstDocumentView(context); + if (contextView?.ComponentView) { + CollectionFreeFormDocumentView.gotoKeyframe((contextView.ComponentView as CollectionFreeFormView).childDocs.slice(), transTime); + context._currentFrame = NumCast(activeItem.presActiveFrame); + } + } } if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) { DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia); @@ -353,8 +361,6 @@ export class PresBox extends ViewBoxBaseComponent() { navigateToView = (targetDoc: Doc, activeItem: Doc) => { clearTimeout(this._navTimer); const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; - if (bestTarget) console.log(bestTarget.title, bestTarget.type); - else console.log('no best target'); if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); }; @@ -363,17 +369,29 @@ export class PresBox extends ViewBoxBaseComponent() { const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any); const clippable = [DocumentType.COMPARISON].includes(target.type as any); - return { scrollable, pannable, temporal, clippable }; + const dataview = [DocumentType.INK].includes(target.type as any) && target.activeFrame === undefined; + return { scrollable, pannable, temporal, clippable, dataview }; } // navigates to the bestTarget document by making sure it is on screen, // then it applies the view specs stored in activeItem to @action static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) { - bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(bestTarget); + const transTime = NumCast(activeItem.presDuration, 500); + const presTransitionTime = `all ${transTime}ms`; + bestTarget._viewTransition = presTransitionTime; + const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(bestTarget); if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; if (temporal) bestTarget._currentTimecode = activeItem.presStartTime; if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll; + if (dataview) { + bestTarget._dataTransition = presTransitionTime; + bestTarget.data = (activeItem.presData as any as InkField)[Copy](); + bestTarget.x = NumCast(activeItem.presX); + bestTarget.y = NumCast(activeItem.presY); + bestTarget.width = NumCast(activeItem.presWidth); + bestTarget.height = NumCast(activeItem.presHeight); + } + if (pannable) { const contentBounds = Cast(activeItem.contentBounds, listSpec('number')); if (contentBounds) { @@ -389,14 +407,17 @@ export class PresBox extends ViewBoxBaseComponent() { bestTarget._viewScale = activeItem.presPinViewScale; } } - return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); + return setTimeout(() => { + bestTarget._viewTransition = undefined; + if (dataview) bestTarget._dataTransition = undefined; + }, transTime + 10); } /// copies values from the targetDoc (which is the prototype of the pinDoc) to /// reserved fields on the pinDoc so that those values can be restored to the /// target doc when navigating to it. @action - static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined) { + static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined, targetDoc: Doc) { if (pinProps?.pinWithView) { // If pinWithView option set then update scale and x / y props of slide const bounds = pinProps.pinWithView.bounds; @@ -407,13 +428,20 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.contentBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); } if (pinProps?.pinDocView) { - const { scrollable, pannable, temporal, clippable } = this.pinDataTypes(pinDoc); - pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable; + const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(pinDoc); + pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || pinProps.activeFrame !== undefined; if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; - else if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; - else if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1; - else if (pannable) { + if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; + if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1; + if (dataview) { + pinDoc.presData = (targetDoc.data as InkField)[Copy](); + pinDoc.presX = NumCast(targetDoc.x); + pinDoc.presY = NumCast(targetDoc.y); + pinDoc.presWidth = NumCast(targetDoc.width); + pinDoc.presHeight = NumCast(targetDoc.height); + } + if (pannable) { const panX = NumCast(pinDoc._panX); const panY = NumCast(pinDoc._panY); const pw = NumCast(pinProps.panelWidth); @@ -493,8 +521,6 @@ export class PresBox extends ViewBoxBaseComponent() { // 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. if (activeItem.presPinView) { - console.log(targetDoc.title); - console.log('presPinView in PresBox.tsx:420'); // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target this.navigateToView(targetDoc, activeItem); } @@ -866,10 +892,9 @@ export class PresBox extends ViewBoxBaseComponent() { //Regular click @action selectElement = async (doc: Doc) => { - const context = Cast(doc.context, Doc, null); this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem); - if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); - else this.updateCurrentPresentation(context); + if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(DocCast(doc.context)), 0); + else this.updateCurrentPresentation(DocCast(doc.context)); }; //Command click -- cgit v1.2.3-70-g09d2 From 7278aaa71a13f57cdc371bd771f5fcc6419707b7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Aug 2022 13:25:23 -0400 Subject: added x,y,w,h view pinning for all documents. --- src/client/views/DocumentButtonBar.tsx | 3 +- src/client/views/nodes/trails/PresBox.tsx | 59 +++++++++++++++--------- src/client/views/nodes/trails/PresElementBox.tsx | 15 +++++- 3 files changed, 52 insertions(+), 25 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 40fc8dae6..76e2d64a2 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -26,6 +26,7 @@ import { DashFieldView } from './nodes/formattedText/DashFieldView'; import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; import { TemplateMenu } from './TemplateMenu'; import React = require('react'); +import { DocumentType } from '../documents/DocumentTypes'; const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -243,7 +244,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocView: true, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinDocView: !docs.some(doc => !e.shiftKey && doc.type === DocumentType.RTF), activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); }}>
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index f254eaba6..eb6dad327 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -6,8 +6,10 @@ import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc'; +import { Copy } from '../../../../fields/FieldSymbols'; import { InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; +import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; @@ -29,8 +31,6 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie import { FieldView, FieldViewProps } from '../FieldView'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; -import { Copy } from '../../../../fields/FieldSymbols'; -import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu'; export interface PinProps { audioRange?: boolean; @@ -370,7 +370,8 @@ export class PresBox extends ViewBoxBaseComponent() { const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any); const clippable = [DocumentType.COMPARISON].includes(target.type as any); const dataview = [DocumentType.INK].includes(target.type as any) && target.activeFrame === undefined; - return { scrollable, pannable, temporal, clippable, dataview }; + const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; + return { scrollable, pannable, temporal, clippable, dataview, textview }; } // navigates to the bestTarget document by making sure it is on screen, // then it applies the view specs stored in activeItem to @@ -378,20 +379,18 @@ export class PresBox extends ViewBoxBaseComponent() { static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) { const transTime = NumCast(activeItem.presDuration, 500); const presTransitionTime = `all ${transTime}ms`; + const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(bestTarget); bestTarget._viewTransition = presTransitionTime; - const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(bestTarget); + bestTarget._dataTransition = dataview || textview ? presTransitionTime : undefined; + bestTarget.x = NumCast(activeItem.presX); + bestTarget.y = NumCast(activeItem.presY); + bestTarget.width = NumCast(activeItem.presWidth); + bestTarget.height = NumCast(activeItem.presHeight); if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; if (temporal) bestTarget._currentTimecode = activeItem.presStartTime; if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll; - if (dataview) { - bestTarget._dataTransition = presTransitionTime; - bestTarget.data = (activeItem.presData as any as InkField)[Copy](); - bestTarget.x = NumCast(activeItem.presX); - bestTarget.y = NumCast(activeItem.presY); - bestTarget.width = NumCast(activeItem.presWidth); - bestTarget.height = NumCast(activeItem.presHeight); - } - + if (dataview) Doc.GetProto(bestTarget).data = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + if (textview) Doc.GetProto(bestTarget).text = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; if (pannable) { const contentBounds = Cast(activeItem.contentBounds, listSpec('number')); if (contentBounds) { @@ -409,7 +408,7 @@ export class PresBox extends ViewBoxBaseComponent() { } return setTimeout(() => { bestTarget._viewTransition = undefined; - if (dataview) bestTarget._dataTransition = undefined; + if (dataview || textview) bestTarget._dataTransition = undefined; }, transTime + 10); } @@ -428,19 +427,18 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.contentBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); } if (pinProps?.pinDocView) { - const { scrollable, pannable, temporal, clippable, dataview } = this.pinDataTypes(pinDoc); - pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || pinProps.activeFrame !== undefined; + const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(pinDoc); + pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || textview || pinProps.activeFrame !== undefined; + pinDoc.presX = NumCast(targetDoc.x); + pinDoc.presY = NumCast(targetDoc.y); + pinDoc.presWidth = NumCast(targetDoc.width); + pinDoc.presHeight = NumCast(targetDoc.height); if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1; - if (dataview) { - pinDoc.presData = (targetDoc.data as InkField)[Copy](); - pinDoc.presX = NumCast(targetDoc.x); - pinDoc.presY = NumCast(targetDoc.y); - pinDoc.presWidth = NumCast(targetDoc.width); - pinDoc.presHeight = NumCast(targetDoc.height); - } + if (textview) pinDoc.presData = targetDoc.text instanceof ObjectField ? targetDoc.text[Copy]() : targetDoc.text; + if (dataview) pinDoc.presData = targetDoc.data instanceof ObjectField ? targetDoc.data[Copy]() : targetDoc.data; if (pannable) { const panX = NumCast(pinDoc._panX); const panY = NumCast(pinDoc._panY); @@ -512,9 +510,24 @@ export class PresBox extends ViewBoxBaseComponent() { // openInTab(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { LightboxView.SetLightboxDoc(undefined); + const transTime = NumCast(activeItem.presDuration, 500); + const presTransitionTime = `all ${transTime}ms`; + targetDoc._dataTransition = presTransitionTime; + targetDoc.x = NumCast(activeItem.presX); + targetDoc.y = NumCast(activeItem.presY); + targetDoc.width = NumCast(activeItem.presWidth); + targetDoc.height = NumCast(activeItem.presHeight); await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { LightboxView.SetLightboxDoc(undefined); + + const transTime = NumCast(activeItem.presDuration, 500); + const presTransitionTime = `all ${transTime}ms`; + targetDoc._dataTransition = presTransitionTime; + targetDoc.x = NumCast(activeItem.presX); + targetDoc.y = NumCast(activeItem.presY); + targetDoc.width = NumCast(activeItem.presWidth); + targetDoc.height = NumCast(activeItem.presHeight); //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right } diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 91196ca21..38a87c34b 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -3,7 +3,7 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; -import { Id } from '../../../../fields/FieldSymbols'; +import { Copy, Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; @@ -24,6 +24,8 @@ import { PresBox } from './PresBox'; import './PresElementBox.scss'; import { PresMovement } from './PresEnums'; import React = require('react'); +import { InkField } from '../../../../fields/InkField'; +import { RichTextField } from '../../../../fields/RichTextField'; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. @@ -320,6 +322,12 @@ export class PresElementBox extends ViewBoxBaseComponent() { case DocumentType.RTF: const scroll = targetDoc._scrollTop; activeItem.presPinViewScroll = scroll; + if (targetDoc.type === DocumentType.RTF) { + activeItem.presData = targetDoc.text instanceof RichTextField ? targetDoc.text[Copy]() : targetDoc.text; + } + break; + case DocumentType.INK: + activeItem.presData = targetDoc.data instanceof InkField ? targetDoc.data[Copy]() : targetDoc.data; break; case DocumentType.VID: case DocumentType.AUDIO: @@ -337,6 +345,11 @@ export class PresElementBox extends ViewBoxBaseComponent() { activeItem.presPinViewY = y; activeItem.presPinViewScale = scale; } + + activeItem.presX = NumCast(targetDoc.x); + activeItem.presY = NumCast(targetDoc.y); + activeItem.presWidth = NumCast(targetDoc.width); + activeItem.presHeight = NumCast(targetDoc.height); }; @computed get recordingIsInOverlay() { -- cgit v1.2.3-70-g09d2 From b1044d1d79c1e06769f74df514e12557426b67be Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Aug 2022 16:16:21 -0400 Subject: trying to clean up transition times for presbox / jumptoDoc, etc. --- src/client/util/DocumentManager.ts | 18 +- .../views/collections/CollectionNoteTakingView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 5 +- src/client/views/collections/TabDocView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 + src/client/views/nodes/trails/PresBox.tsx | 662 +++++++++------------ src/client/views/nodes/trails/PresElementBox.tsx | 9 - 8 files changed, 287 insertions(+), 417 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 52b643c04..2ca5d1095 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -154,7 +154,7 @@ export class DocumentManager { CollectionDockingView.AddSplit(doc, 'right'); finished?.(); }; - public jumpToDocument = async ( + public jumpToDocument = ( targetDoc: Doc, // document to display willZoom: boolean, // whether to zoom doc to take up most of screen createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist @@ -165,8 +165,8 @@ export class DocumentManager { finished?: () => void, originalTarget?: Doc, noSelect?: boolean, - presZoom?: number - ): Promise => { + presZoomScale?: number + ): void => { originalTarget = originalTarget ?? targetDoc; const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; const docView = getFirstDocView(targetDoc, originatingDoc); @@ -207,7 +207,7 @@ export class DocumentManager { finished?.(); }; const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && getFirstDocView(annotatedDoc); - const contextDocs = docContext.length ? await DocListCastAsync(docContext[0].data) : undefined; + const contextDocs = docContext.length ? DocListCast(docContext[0].data) : undefined; const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined; const targetDocContext = contextDoc || annotatedDoc; const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above @@ -218,7 +218,7 @@ export class DocumentManager { annoContainerView.focus(targetDoc, { originalTarget, willZoom, - scale: presZoom, + scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(true); @@ -237,7 +237,7 @@ export class DocumentManager { focusView.focus(originalTarget ?? targetDoc, { originalTarget, willZoom, - scale: presZoom, + scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(forceDidFocus || didFocus); @@ -265,7 +265,9 @@ export class DocumentManager { afterFocus: async () => { targetDocContext._viewTransition = undefined; if (targetDocContext.layoutKey === 'layout_icon') { - targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom)); + targetDocContextView.iconify(() => + this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale) + ); } return ViewAdjustment.doNothing; }, @@ -309,7 +311,7 @@ export class DocumentManager { const docContextView = this.getFirstDocumentView(docContext[0]); if (docContextView) { return docContextView.iconify(() => - this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom) + this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale) ); } } diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 615141485..92c0bc341 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -201,7 +201,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); } } const endFocus = async (moved: boolean) => (options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 71834607c..7f142727c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -259,7 +259,7 @@ export class CollectionStackingView extends CollectionSubView options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; @@ -494,8 +494,7 @@ export class CollectionStackingView extends CollectionSubView { if (targInd === -1) { this.addDocument(docs); - } - else { + } else { const childDocs = this.childDocList; if (childDocs) { childDocs.splice(targInd, 0, ...docs); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index f7b48adf6..49228a808 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -366,7 +366,7 @@ export class TabDocView extends React.Component { focusFunc = (doc: Doc, options?: DocFocusOptions) => { const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap; if (shrinkwrap && this._document) { - const focusSpeed = 1000; + const focusSpeed = NumCast(this._document.focusSpeed, 500); shrinkwrap(); this._document._viewTransition = `transform ${focusSpeed}ms`; setTimeout( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 210370d39..ede113a9f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1146,7 +1146,7 @@ export class CollectionFreeFormView extends CollectionSubView() { constructor(props: any) { super(props); if ((Doc.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this)); - this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox } @observable public static Instance: PresBox; @@ -127,12 +126,7 @@ export class PresBox extends ViewBoxBaseComponent() { @observable _treeViewMap: Map = new Map(); @computed get tagDocs() { - const tagDocs: Doc[] = []; - for (const doc of this.childDocs) { - const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); - tagDocs.push(tagDoc); - } - return tagDocs; + return this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null)); } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); @@ -145,11 +139,11 @@ export class PresBox extends ViewBoxBaseComponent() { } @computed get scrollable(): boolean { if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; - else return false; + return false; } @computed get panable(): boolean { if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; - else return false; + return false; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; @@ -202,8 +196,7 @@ export class PresBox extends ViewBoxBaseComponent() { @action updateCurrentPresentation = (pres?: Doc) => { - if (pres) Doc.ActivePresentation = pres; - else Doc.ActivePresentation = this.rootDoc; + Doc.ActivePresentation = pres ?? this.rootDoc; document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); document.addEventListener('keydown', PresBox.keyEventsWrapper, true); this._presKeyEventsActive = true; @@ -318,7 +311,7 @@ export class PresBox extends ViewBoxBaseComponent() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (activeItem.presActiveFrame !== undefined) { - const transTime = NumCast(activeItem.presDuration, 500); + const transTime = NumCast(activeItem.presTransition, 500); const context = DocCast(DocCast(activeItem.presentationTargetDoc).context); if (context) { const contextView = DocumentManager.Instance.getFirstDocumentView(context); @@ -340,12 +333,8 @@ export class PresBox extends ViewBoxBaseComponent() { } if (targetDoc) { Doc.linkFollowHighlight(targetDoc.annotationOn instanceof Doc ? [targetDoc, targetDoc.annotationOn] : targetDoc); - targetDoc && - runInAction(() => { - if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0; - else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; - }); - setTimeout(() => (targetDoc.focusSpeed = 500), this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510); + targetDoc && runInAction(() => (targetDoc.focusSpeed = activeItem.presMovement === PresMovement.Jump ? 0 : NumCast(activeItem.presTransition, 500))); + setTimeout(() => (targetDoc.focusSpeed = undefined), NumCast(targetDoc.focusSpeed) + 10); } if (targetDoc?.lastFrame !== undefined) { targetDoc._currentFrame = 0; @@ -356,14 +345,6 @@ export class PresBox extends ViewBoxBaseComponent() { this.onHideDocument(); //Handles hide after/before } }); - - _navTimer!: NodeJS.Timeout; - navigateToView = (targetDoc: Doc, activeItem: Doc) => { - clearTimeout(this._navTimer); - const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; - if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); - }; - static pinDataTypes(target: Doc) { const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking; const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); @@ -373,43 +354,29 @@ export class PresBox extends ViewBoxBaseComponent() { const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; return { scrollable, pannable, temporal, clippable, dataview, textview }; } - // navigates to the bestTarget document by making sure it is on screen, - // then it applies the view specs stored in activeItem to + @action - static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) { - const transTime = NumCast(activeItem.presDuration, 500); + static restoreTargetDocView(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) { + const transTime = NumCast(activeItem.presTransition, 500); const presTransitionTime = `all ${transTime}ms`; const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(bestTarget); bestTarget._viewTransition = presTransitionTime; - bestTarget._dataTransition = dataview || textview ? presTransitionTime : undefined; - bestTarget.x = NumCast(activeItem.presX); - bestTarget.y = NumCast(activeItem.presY); - bestTarget.width = NumCast(activeItem.presWidth); - bestTarget.height = NumCast(activeItem.presHeight); if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; if (temporal) bestTarget._currentTimecode = activeItem.presStartTime; if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll; if (dataview) Doc.GetProto(bestTarget).data = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; if (textview) Doc.GetProto(bestTarget).text = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; if (pannable) { - const contentBounds = Cast(activeItem.contentBounds, listSpec('number')); + bestTarget._panX = activeItem.presPinViewX; + bestTarget._panY = activeItem.presPinViewY; + bestTarget._viewScale = activeItem.presPinViewScale; + const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { - bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2; - bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2; const dv = DocumentManager.Instance.getDocumentView(bestTarget); - if (dv) { - bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0])); - } - } else { - bestTarget._panX = activeItem.presPinViewX; - bestTarget._panY = activeItem.presPinViewY; - bestTarget._viewScale = activeItem.presPinViewScale; + dv && (bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0]))); } } - return setTimeout(() => { - bestTarget._viewTransition = undefined; - if (dataview || textview) bestTarget._dataTransition = undefined; - }, transTime + 10); + return setTimeout(() => (bestTarget._viewTransition = undefined), transTime + 10); } /// copies values from the targetDoc (which is the prototype of the pinDoc) to @@ -424,7 +391,7 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presPinViewX = bounds.left + bounds.width / 2; pinDoc.presPinViewY = bounds.top + bounds.height / 2; pinDoc.presPinViewScale = pinProps.pinWithView.scale; - pinDoc.contentBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); + pinDoc.presPinViewBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); } if (pinProps?.pinDocView) { const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(pinDoc); @@ -446,7 +413,7 @@ export class PresBox extends ViewBoxBaseComponent() { const ph = NumCast(pinProps.panelHeight); const ps = NumCast(pinDoc._viewScale); if (pw && ph && ps) { - pinDoc.contentBounds = new List([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]); + pinDoc.presPinViewBounds = new List([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]); } pinDoc.presPinViewX = panX; pinDoc.presPinViewY = panY; @@ -455,6 +422,7 @@ export class PresBox extends ViewBoxBaseComponent() { } } + _navTimer!: NodeJS.Timeout; /** * This method makes sure that cursor navigates to the element that * has the option open and last in the group. @@ -504,38 +472,31 @@ export class PresBox extends ViewBoxBaseComponent() { finished?.(); } }; - // If openDocument is selected then it should open the document for the user - if (activeItem.openDocument) { - LightboxView.SetLightboxDoc(targetDoc); - // openInTab(targetDoc); - } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { - LightboxView.SetLightboxDoc(undefined); - const transTime = NumCast(activeItem.presDuration, 500); + if (activeItem.presPinView && DocCast(targetDoc.context)?._currentFrame === undefined) { + const transTime = NumCast(activeItem.presTransition, 500); const presTransitionTime = `all ${transTime}ms`; targetDoc._dataTransition = presTransitionTime; - targetDoc.x = NumCast(activeItem.presX); - targetDoc.y = NumCast(activeItem.presY); - targetDoc.width = NumCast(activeItem.presWidth); - targetDoc.height = NumCast(activeItem.presHeight); - await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right - } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { + targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x)); + targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y)); + targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width)); + targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height)); + setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10); + } + // If openDocument is selected then it should open the document for the user + if (activeItem.openDocument) { + LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc); + } else if (targetDoc && curDoc.presMovement !== PresMovement.None && targetDoc) { LightboxView.SetLightboxDoc(undefined); - - const transTime = NumCast(activeItem.presDuration, 500); - const presTransitionTime = `all ${transTime}ms`; - targetDoc._dataTransition = presTransitionTime; - targetDoc.x = NumCast(activeItem.presX); - targetDoc.y = NumCast(activeItem.presY); - targetDoc.width = NumCast(activeItem.presWidth); - targetDoc.height = NumCast(activeItem.presHeight); - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right + const zooming = curDoc.presMovement !== PresMovement.Pan; + DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); } // 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. if (activeItem.presPinView) { // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target - this.navigateToView(targetDoc, activeItem); + clearTimeout(this._navTimer); + const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; + if (bestTarget) this._navTimer = PresBox.restoreTargetDocView(bestTarget, activeItem, false); } }; @@ -1488,12 +1449,8 @@ export class PresBox extends ViewBoxBaseComponent() { style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'block' }} className={'toolbar-slider'} id="duration-slider" - onPointerDown={() => { - this._batch = UndoManager.StartBatch('presDuration'); - }} - onPointerUp={() => { - if (this._batch) this._batch.end(); - }} + onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))} + onPointerUp={() => this._batch?.end()} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); this.setDurationTime(e.target.value); @@ -1650,28 +1607,12 @@ export class PresBox extends ViewBoxBaseComponent() { }); }; - @computed get mediaStopSlides() { - const activeItem: Doc = this.activeItem; - const list = this.childDocs.map((doc, i) => { - if (i > this.itemIndex) { - return ( - - ); - } - }); - return list; - } - @computed get mediaOptionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const clipStart: number = NumCast(activeItem.clipStart); const clipEnd: number = NumCast(activeItem.clipEnd); - const duration = Math.round(NumCast(activeItem[`${Doc.LayoutFieldKey(activeItem)}-duration`]) * 10); const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc); - const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + '. ' + this.childDocs[mediaStopDocInd - 1].title : ''; if (activeItem && targetDoc) { return (
@@ -1842,55 +1783,53 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get newDocumentToolbarDropdown() { return ( -
-
e.stopPropagation()} - onPointerUp={e => e.stopPropagation()} - onPointerDown={e => e.stopPropagation()}> -
-
{ - this.layout = 'blank'; - this.createNewSlide(this.layout); - })} - /> -
{ - this.layout = 'title'; - this.createNewSlide(this.layout); - })}> -
Title
-
Subtitle
-
-
{ - this.layout = 'header'; - this.createNewSlide(this.layout); - })}> -
- Section header -
+
e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} + onPointerDown={e => e.stopPropagation()}> +
+
{ + this.layout = 'blank'; + this.createNewSlide(this.layout); + })} + /> +
{ + this.layout = 'title'; + this.createNewSlide(this.layout); + })}> +
Title
+
Subtitle
+
+
{ + this.layout = 'header'; + this.createNewSlide(this.layout); + })}> +
+ Section header
-
{ - this.layout = 'content'; - this.createNewSlide(this.layout); - })}> -
- Title -
-
Text goes here
+
+
{ + this.layout = 'content'; + this.createNewSlide(this.layout); + })}> +
+ Title
+
Text goes here
@@ -1904,73 +1843,71 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get newDocumentDropdown() { return ( -
-
e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> -
- Slide Title:

- { - e.stopPropagation(); - e.preventDefault(); - runInAction(() => (this.title = e.target.value)); - }}> +
e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
+ Slide Title:

+ { + e.stopPropagation(); + e.preventDefault(); + runInAction(() => (this.title = e.target.value)); + }}> +
+
+ Choose type: +
+
(this.addFreeform = !this.addFreeform))}> + Text +
+
(this.addFreeform = !this.addFreeform))}> + Freeform +
-
- Choose type: -
-
(this.addFreeform = !this.addFreeform))}> - Text -
-
(this.addFreeform = !this.addFreeform))}> - Freeform +
+
+ Preset layouts: +
+
(this.layout = 'blank'))} /> +
(this.layout = 'title'))}> +
Title
+
Subtitle
+
+
(this.layout = 'header'))}> +
+ Section header
-
-
- Preset layouts: -
-
(this.layout = 'blank'))} /> -
(this.layout = 'title'))}> -
Title
-
Subtitle
+
(this.layout = 'content'))}> +
+ Title
-
(this.layout = 'header'))}> -
- Section header -
+
Text goes here
+
+
(this.layout = 'twoColumns'))}> +
+ Title
-
(this.layout = 'content'))}> -
- Title -
-
Text goes here
+
+ Column one text
-
(this.layout = 'twoColumns'))}> -
- Title -
-
- Column one text -
-
- Column two text -
+
+ Column two text
-
(this.openLayouts = !this.openLayouts))}> - -
-
-
this.createNewSlide(this.layout, this.title, this.addFreeform)}> - Create New Slide -
+
(this.openLayouts = !this.openLayouts))}> + +
+
+
+
this.createNewSlide(this.layout, this.title, this.addFreeform)}> + Create New Slide
@@ -2105,115 +2042,109 @@ export class PresBox extends ViewBoxBaseComponent() { const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black'; const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black'; return ( -
-
e.stopPropagation()} - onPointerUp={e => e.stopPropagation()} - onPointerDown={e => e.stopPropagation()}> -
- {this.stringType} selected -
-
- Contents -
-
- Edit -
-
-
-
Active text color
-
{ - this.openActiveColorPicker = !this.openActiveColorPicker; - })}>
+
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
+ {this.stringType} selected +
+
+ Contents
- {this.activeColorPicker} -
-
Viewed font color
-
(this.openViewedColorPicker = !this.openViewedColorPicker))}>
+
+ Edit
- {this.viewedColorPicker} +
+
+
Active text color
-
- Zoom -
-
- Edit -
+ className="ribbon-colorBox" + style={{ backgroundColor: activeFontColor, height: 15, width: 15 }} + onClick={action(() => { + this.openActiveColorPicker = !this.openActiveColorPicker; + })}>
+
+ {this.activeColorPicker} +
+
Viewed font color
+
(this.openViewedColorPicker = !this.openViewedColorPicker))}>
+
+ {this.viewedColorPicker} +
+
+ Zoom
-
-
- Scroll -
-
- Edit -
+
+ Edit
-
- Frames -
-
-
{ - e.stopPropagation(); - this.prevKeyframe(targetDoc, activeItem); - }}> - -
-
(targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}> - {NumCast(targetDoc._currentFrame)} -
-
{ - e.stopPropagation(); - this.nextKeyframe(targetDoc, activeItem); - }}> - -
-
- -
{'Last frame'}
- - }> -
{NumCast(targetDoc.lastFrame)}
-
+
+
+ Scroll
-
- {this.frameListHeader} - {this.frameList} +
+ Edit
-
console.log(' TODO: play frames')}> - Play +
+
+
+ Frames +
+
+
{ + e.stopPropagation(); + this.prevKeyframe(targetDoc, activeItem); + }}> + +
+
(targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}> + {NumCast(targetDoc._currentFrame)} +
+
{ + e.stopPropagation(); + this.nextKeyframe(targetDoc, activeItem); + }}> + +
+ +
{'Last frame'}
+ + }> +
{NumCast(targetDoc.lastFrame)}
+
+
+
+ {this.frameListHeader} + {this.frameList} +
+
console.log(' TODO: play frames')}> + Play
@@ -2233,7 +2164,6 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch @action switchPresented = (color: ColorState) => { - const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const val = String(color.hex); targetDoc['pres-text-viewed-color'] = val; @@ -2241,25 +2171,21 @@ export class PresBox extends ViewBoxBaseComponent() { }; @computed get activeColorPicker() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; return !this.openActiveColorPicker ? null : ( ); } @computed get viewedColorPicker() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; return !this.openViewedColorPicker ? null : ( ); } @@ -2422,10 +2348,7 @@ export class PresBox extends ViewBoxBaseComponent() { }; @observable - toggleDisplayMovement = (doc: Doc) => { - if (doc.displayMovement) doc.displayMovement = false; - else doc.displayMovement = true; - }; + toggleDisplayMovement = (doc: Doc) => (doc.displayMovement = !doc.displayMovement); @action checkList = (doc: Doc, list: any): number => { @@ -2538,24 +2461,13 @@ export class PresBox extends ViewBoxBaseComponent() { } }; - @computed get moreInfoDropdown() { - return
; - } - @computed get toolbarWidth(): number { - const width = this.props.PanelWidth(); - return width; + return this.props.PanelWidth(); } @action - toggleProperties = () => { - if (SettingsManager.propertiesWidth > 0) { - SettingsManager.propertiesWidth = 0; - } else { - SettingsManager.propertiesWidth = 250; - } - }; + toggleProperties = () => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250); @computed get toolbar() { const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; @@ -2677,10 +2589,7 @@ export class PresBox extends ViewBoxBaseComponent() { } @action - getList = (list: any): List => { - const x: List = list; - return x; - }; + getList = (list: any): List => list; @action updateList = (list: any): List => { @@ -2709,12 +2618,7 @@ export class PresBox extends ViewBoxBaseComponent() {
  Frames {this.panable ? Panable : this.scrollable ? Scrollable : null}
- -
{'Add frame by example'}
- - }> + {'Add frame by example'}
}>
{ @@ -2724,12 +2628,7 @@ export class PresBox extends ViewBoxBaseComponent() { e.stopPropagation()} />
- -
{'Edit in collection'}
- - }> + {'Edit in collection'}
}>
{ @@ -2746,7 +2645,6 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get frameList() { const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; const frameList: List = this.getList(activeItem.frameList); if (frameList) { const frameItems = frameList.map(value =>
); @@ -2755,18 +2653,14 @@ export class PresBox extends ViewBoxBaseComponent() { } @computed get playButtonFrames() { - const targetDoc: Doc = this.targetDoc; - return ( - <> - {this.targetDoc ? ( -
= 0 ? 'inline-flex' : 'none' }}> -
{NumCast(targetDoc._currentFrame)}
-
-
{NumCast(targetDoc.lastFrame)}
-
- ) : null} - - ); + const targetDoc = this.targetDoc; + return this.targetDoc ? ( +
= 0 ? 'inline-flex' : 'none' }}> +
{NumCast(targetDoc._currentFrame)}
+
+
{NumCast(targetDoc.lastFrame)}
+
+ ) : null; } @computed get playButtons() { @@ -2775,12 +2669,7 @@ export class PresBox extends ViewBoxBaseComponent() { // Case 1: There are still other frames and should go through all frames before going to next slide return (
- -
{'Loop'}
- - }> + {'Loop'}
}>
(this.layoutDoc.presLoop = !this.layoutDoc.presLoop)}>
@@ -2798,12 +2687,7 @@ export class PresBox extends ViewBoxBaseComponent() { }}>
- -
{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
- - }> + {this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
}>
@@ -2821,12 +2705,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- -
{'Click to return to 1st slide'}
- - }> + {'Click to return to 1st slide'}
}>
this.gotoDocument(0, this.activeItem)}> 1
@@ -2931,12 +2810,7 @@ export class PresBox extends ViewBoxBaseComponent() { return DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc) ? (
e.stopPropagation()}>
- -
{'Loop'}
- - }> + {'Loop'}
}>
() {
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}>
- -
{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
- - }> + {this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}>
@@ -2962,12 +2831,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- -
{'Click to return to 1st slide'}
- - }> + {'Click to return to 1st slide'}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}> 1
@@ -3033,5 +2897,17 @@ ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) { CollectionDockingView.AddSplit(doc, 'right'); finished?.(); }; - DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, () => PresBox.navigateToDoc(bestTarget, activeItem, true), undefined, true, NumCast(activeItem.presZoom)); + DocumentManager.Instance.jumpToDocument( + bestTarget, + true, + openInTab, + srcContext ? [srcContext] : [], + undefined, + undefined, + undefined, + () => PresBox.restoreTargetDocView(bestTarget, activeItem, true), + undefined, + true, + NumCast(activeItem.presZoom) + ); }); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 38a87c34b..c78828a78 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -162,15 +162,6 @@ export class PresElementBox extends ViewBoxBaseComponent() { )); return groupSlides; } - @computed get duration() { - let durationInS: number; - if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { - durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); - durationInS = Math.round(durationInS * 10) / 10; - } else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000; - else durationInS = 2; - return 'D: ' + durationInS + 's'; - } @computed get transition() { let transitionInS: number; -- cgit v1.2.3-70-g09d2 From 3f5cbb9ae99b7ed33fa09c1d3cf5f27414881c00 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Aug 2022 16:34:15 -0400 Subject: from last --- src/client/views/nodes/WebBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 6c2e42f86..b9e8e7c6e 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -285,7 +285,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { - focusSpeed = 500; - if (!this._pdfViewer) this._initialScroll = scrollTo; - else if (smooth) smoothScroll(focusSpeed, mainCont, scrollTo); + else if (smooth) smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), mainCont, scrollTo); else this._mainCont.current?.scrollTo({ top: Math.abs(scrollTo || 0) }); } } else { -- cgit v1.2.3-70-g09d2 From a4ce2913b8a15cdd4670002a4a74f1d86601348e Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Aug 2022 18:34:41 -0400 Subject: a bunch of mostly decorative cleanup to presBox --- src/client/views/PropertiesView.tsx | 6 +- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 558 +++++++---------------- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/client/views/nodes/trails/PresEnums.ts | 42 +- 5 files changed, 186 insertions(+), 424 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 33f17047b..2708c561d 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -58,7 +58,7 @@ export class PropertiesView extends React.Component { } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; - if (PresBox.Instance?._selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); + if (PresBox.Instance?.selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); return undefined; } @computed get isPres(): boolean { @@ -1612,7 +1612,7 @@ export class PropertiesView extends React.Component { ); } if (this.isPres) { - const selectedItem: boolean = PresBox.Instance?._selectedArray.size > 0; + const selectedItem: boolean = PresBox.Instance?.selectedArray.size > 0; const type = PresBox.Instance.activeItem?.type; return (
@@ -1622,7 +1622,7 @@ export class PropertiesView extends React.Component {
{this.editableTitle}
-
{PresBox.Instance?._selectedArray.size} selected
+
{PresBox.Instance?.selectedArray.size} selected
{PresBox.Instance?.listOfSelected}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 49228a808..bead5825c 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -239,7 +239,7 @@ export class TabDocView extends React.Component { pinDoc.treeViewGrowsHorizontally = true; // the document expands horizontally when displayed as a tree view header pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header const presArray: Doc[] = PresBox.Instance?.sortArray(); - const size: number = PresBox.Instance?._selectedArray.size; + const size: number = PresBox.Instance?.selectedArray.size; const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index ade098917..ac68ea281 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -12,7 +12,7 @@ import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; +import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents, StopEvent } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; @@ -86,6 +86,7 @@ export class PresBox extends ViewBoxBaseComponent() { } private _disposers: { [name: string]: IReactionDisposer } = {}; + public selectedArray = new ObservableSet(); constructor(props: any) { super(props); @@ -93,6 +94,7 @@ export class PresBox extends ViewBoxBaseComponent() { } @observable public static Instance: PresBox; + @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView @observable _isChildActive = false; @observable _moveOnFromAudio: boolean = true; @@ -103,14 +105,13 @@ export class PresBox extends ViewBoxBaseComponent() { @observable _dragArray: HTMLElement[] = []; @observable _pathBoolean: boolean = false; @observable _expandBoolean: boolean = false; - - @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView - @observable private transitionTools: boolean = false; - @observable private newDocumentTools: boolean = false; - @observable private progressivizeTools: boolean = false; - @observable private openMovementDropdown: boolean = false; - @observable private openEffectDropdown: boolean = false; - @observable private presentTools: boolean = false; + @observable _transitionTools: boolean = false; + @observable _newDocumentTools: boolean = false; + @observable _progressivizeTools: boolean = false; + @observable _openMovementDropdown: boolean = false; + @observable _openEffectDropdown: boolean = false; + @observable _presentTools: boolean = false; + @observable _treeViewMap: Map = new Map(); @computed get isTreeOrStack() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any); } @@ -123,8 +124,6 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get childDocs() { return DocListCast(this.rootDoc[this.presFieldKey]); } - @observable _treeViewMap: Map = new Map(); - @computed get tagDocs() { return this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null)); } @@ -137,22 +136,21 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } - @computed get scrollable(): boolean { + @computed get scrollable() { if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; return false; } - @computed get panable(): boolean { + @computed get panable() { if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; return false; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; - if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); + if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); } - @computed get isPres(): boolean { + @computed get isPres() { document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); if (this.selectedDoc?.type === DocumentType.PRES) { - document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); document.addEventListener('keydown', PresBox.keyEventsWrapper, true); return true; } @@ -161,10 +159,9 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } - _selectedArray = new ObservableSet(); - clearSelectedArray = () => this._selectedArray.clear(); - addToSelectedArray = (doc: Doc) => this._selectedArray.add(doc); - removeFromSelectedArray = (doc: Doc) => this._selectedArray.delete(doc); + clearSelectedArray = () => this.selectedArray.clear(); + addToSelectedArray = (doc: Doc) => this.selectedArray.add(doc); + removeFromSelectedArray = (doc: Doc) => this.selectedArray.delete(doc); _unmounting = false; @action @@ -448,7 +445,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.layoutDoc.presCollection = srcContext; } const presStatus = this.rootDoc.presStatus; - const selViewCache = Array.from(this._selectedArray); + const selViewCache = Array.from(this.selectedArray); const dragViewCache = Array.from(this._dragArray); const eleViewCache = Array.from(this._eleArray); const self = this; @@ -756,27 +753,11 @@ export class PresBox extends ViewBoxBaseComponent() { } }); - setMovementName = action((movement: any, activeItem: Doc): string => { - let output: string = 'none'; - switch (movement) { - case PresMovement.Zoom: - output = 'Pan & Zoom'; - break; //Pan and zoom - case PresMovement.Pan: - output = 'Pan'; - break; //Pan - case PresMovement.Jump: - output = 'Jump cut'; - break; //Jump Cut - case PresMovement.None: - output = 'None'; - break; //None - default: - output = 'Zoom'; - activeItem.presMovement = 'zoom'; - break; //default set as zoom + movementName = action((activeItem: Doc) => { + if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) { + activeItem.presMovement = 'zoom'; } - return output; + return StrCast(activeItem.presMovement); }); whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive))); @@ -822,16 +803,13 @@ export class PresBox extends ViewBoxBaseComponent() { /** * For sorting the array so that the order is maintained when it is dropped. */ - @action - sortArray = (): Doc[] => { - return this.childDocs.filter(doc => this._selectedArray.has(doc)); - }; + sortArray = () => this.childDocs.filter(doc => this.selectedArray.has(doc)); /** * Method to get the list of selected items in the order in which they have been selected */ @computed get listOfSelected() { - return Array.from(this._selectedArray).map((doc: Doc, index: any) => { + return Array.from(this.selectedArray).map((doc: Doc, index: any) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (curDoc && curDoc === this.activeItem) @@ -874,24 +852,18 @@ export class PresBox extends ViewBoxBaseComponent() { //Command click @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { - if (!this._selectedArray.has(doc)) { + if (!this.selectedArray.has(doc)) { this.addToSelectedArray(doc); this._eleArray.push(ref); this._dragArray.push(drag); } else { this.removeFromSelectedArray(doc); - this.removeFromArray(this._eleArray, doc); - this.removeFromArray(this._dragArray, doc); + this._eleArray.splice(this._eleArray.indexOf(ref)); + this._dragArray.splice(this._dragArray.indexOf(drag)); } this.selectPres(); }; - removeFromArray = (arr: any[], val: any) => { - const index: number = arr.indexOf(val); - const ret: any[] = arr.splice(index, 1); - arr = ret; - }; - //Shift click @action shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { @@ -924,9 +896,7 @@ export class PresBox extends ViewBoxBaseComponent() { else this.regularSelect(doc, ref, drag, focus); }; - static keyEventsWrapper = (e: KeyboardEvent) => { - PresBox.Instance.keyEvents(e); - }; + static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance.keyEvents(e); // Key for when the presentaiton is active @action @@ -940,7 +910,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (this.layoutDoc.presStatus === 'edit') { undoBatch( action(() => { - for (const doc of this._selectedArray) { + for (const doc of this.selectedArray) { this.removeDocument(doc); } this.clearSelectedArray(); @@ -1027,13 +997,7 @@ export class PresBox extends ViewBoxBaseComponent() { } }; - getAllIndexes = (arr: Doc[], val: Doc): number[] => { - const indexes = []; - for (let i = 0; i < arr.length; i++) { - arr[i] === val && indexes.push(i); - } - return indexes; - }; + getAllIndexes = (arr: Doc[], val: Doc) => arr.map((doc, i) => (doc === val ? i : -1)).filter(i => i !== -1); // Adds the index in the pres path graphically @computed get order() { @@ -1152,7 +1116,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; - this._selectedArray.forEach(doc => (doc.presTransition = timeInMS)); + this.selectedArray.forEach(doc => (doc.presTransition = timeInMS)); }; // Converts seconds to ms and updates presTransition @@ -1161,7 +1125,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) scale += change; if (scale < 0.01) scale = 0.01; if (scale > 1.5) scale = 1.5; - this._selectedArray.forEach(doc => (doc.presZoom = scale)); + this.selectedArray.forEach(doc => (doc.presZoom = scale)); }; // Converts seconds to ms and updates presDuration @@ -1170,100 +1134,43 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - this._selectedArray.forEach(doc => (doc.presDuration = timeInMS)); + this.selectedArray.forEach(doc => (doc.presDuration = timeInMS)); }; /** * When the movement dropdown is changes */ @undoBatch - updateMovement = action((movement: any, all?: boolean) => { - (all ? this.childDocs : this._selectedArray).forEach(doc => { - switch (movement) { - case PresMovement.Zoom: //Pan and zoom - doc.presMovement = PresMovement.Zoom; - break; - case PresMovement.Pan: //Pan - doc.presMovement = PresMovement.Pan; - break; - case PresMovement.Jump: //Jump Cut - doc.presJump = true; - doc.presMovement = PresMovement.Jump; - break; - case PresMovement.None: - default: - doc.presMovement = PresMovement.None; - break; - } - }); - }); + updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presMovement = movement))); @undoBatch @action updateHideBefore = (activeItem: Doc) => { activeItem.presHideBefore = !activeItem.presHideBefore; - this._selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); + this.selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); }; @undoBatch @action updateHideAfter = (activeItem: Doc) => { activeItem.presHideAfter = !activeItem.presHideAfter; - this._selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); + this.selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); }; @undoBatch @action updateOpenDoc = (activeItem: Doc) => { activeItem.openDocument = !activeItem.openDocument; - this._selectedArray.forEach(doc => { - doc.openDocument = activeItem.openDocument; - }); + this.selectedArray.forEach(doc => (doc.openDocument = activeItem.openDocument)); }; @undoBatch @action - updateEffectDirection = (effect: any, all?: boolean) => { - (all ? this.childDocs : this._selectedArray).forEach(doc => { - const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null); - switch (effect) { - case PresEffect.Left: - tagDoc.presEffectDirection = PresEffect.Left; - break; - case PresEffect.Right: - tagDoc.presEffectDirection = PresEffect.Right; - break; - case PresEffect.Top: - tagDoc.presEffectDirection = PresEffect.Top; - break; - case PresEffect.Bottom: - tagDoc.presEffectDirection = PresEffect.Bottom; - break; - case PresEffect.Center: - default: - tagDoc.presEffectDirection = PresEffect.Center; - break; - } - }); - }; + updateEffectDirection = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffectDirection = effect)); @undoBatch @action - updateEffect = (effect: any, all?: boolean) => { - (all ? this.childDocs : this._selectedArray).forEach(doc => { - const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null); - //prettier-ignore - switch (effect) { - default: - case PresEffect.None: tagDoc.presEffect = PresEffect.None; break; - case PresEffect.Bounce: tagDoc.presEffect = PresEffect.Bounce; break; - case PresEffect.Fade: tagDoc.presEffect = PresEffect.Fade; break; - case PresEffect.Flip: tagDoc.presEffect = PresEffect.Flip; break; - case PresEffect.Roll: tagDoc.presEffect = PresEffect.Roll; break; - case PresEffect.Rotate: tagDoc.presEffect = PresEffect.Rotate; break; - } - }); - }; + updateEffect = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffect = effect)); _batch: UndoManager.Batch | undefined = undefined; @@ -1272,6 +1179,46 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; const isPresCollection: boolean = targetDoc === this.layoutDoc.presCollection; const isPinWithView: boolean = BoolCast(activeItem.presPinView); + const presEffect = (effect: PresEffect) => ( +
this.updateEffect(effect)}> + {effect} +
+ ); + const presMovement = (movement: PresMovement) => ( +
this.updateMovement(movement)}> + {movement} +
+ ); + const presDirection = (diretion: PresEffect, icon: string, gridColumn: number, gridRow: number, opts: object) => { + const color = this.activeItem.presEffectDirection === diretion || (diretion === PresEffect.Center && !this.activeItem.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; + return ( + {diretion}
}> +
this.updateEffectDirection(diretion)}> + {icon ? : null} +
+ + ); + }; + const inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void) => { + return ( + (this._batch = UndoManager.StartBatch('pres slider'))} + onPointerUp={() => this._batch?.end()} + onChange={e => { + e.stopPropagation(); + change(e.target.value); + }} + /> + ); + }; if (activeItem && targetDoc) { const type = targetDoc.type; const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; @@ -1282,13 +1229,13 @@ export class PresBox extends ViewBoxBaseComponent() { activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return (
e.stopPropagation()} - onPointerUp={e => e.stopPropagation()} + className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`} + onPointerDown={StopEvent} + onPointerUp={StopEvent} onClick={action(e => { e.stopPropagation(); - this.openMovementDropdown = false; - this.openEffectDropdown = false; + this._openMovementDropdown = false; + this._openEffectDropdown = false; })}>
Movement @@ -1301,24 +1248,16 @@ export class PresBox extends ViewBoxBaseComponent() { className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); - this.openMovementDropdown = !this.openMovementDropdown; + this._openMovementDropdown = !this._openMovementDropdown; })} - style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> - {this.setMovementName(activeItem.presMovement, activeItem)} - -
e.stopPropagation()} style={{ display: this.openMovementDropdown ? 'grid' : 'none' }}> -
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}> - None -
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}> - Pan {'&'} Zoom -
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}> - Pan -
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}> - Jump cut -
+ style={{ borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, border: this._openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + {this.movementName(activeItem)} + +
+ {presMovement(PresMovement.None)} + {presMovement(PresMovement.Zoom)} + {presMovement(PresMovement.Pan)} + {presMovement(PresMovement.Jump)}
)} @@ -1336,21 +1275,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- (this._batch = UndoManager.StartBatch('presZoom'))} - onPointerUp={() => this._batch?.end()} - onChange={(e: React.ChangeEvent) => { - e.stopPropagation(); - this.setZoom(e.target.value); - }} - /> + {inputter('0', '1', '150', zoom, activeItem.presMovement === PresMovement.Zoom, this.setZoom)}
Movement Speed
@@ -1365,21 +1290,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- (this._batch = UndoManager.StartBatch('presTransition'))} - onPointerUp={() => this._batch?.end()} - onChange={(e: React.ChangeEvent) => { - e.stopPropagation(); - this.setTransitionTime(e.target.value); - }} - /> + {inputter('0.1', '0.1', '10', transitionSpeed, [PresMovement.Pan, PresMovement.Zoom].includes(activeItem.presMovement as any), this.setTransitionTime)}
Fast
Medium
@@ -1390,35 +1301,20 @@ export class PresBox extends ViewBoxBaseComponent() { Visibility {'&'} Duration
{isPresCollection ? null : ( - -
{'Hide before presented'}
- - }> + {'Hide before presented'}
}>
this.updateHideBefore(activeItem)}> Hide before
)} {isPresCollection ? null : ( - -
{'Hide after presented'}
- - }> + {'Hide after presented'}
}>
this.updateHideAfter(activeItem)}> Hide after
)} - -
{'Open in lightbox view'}
- - }> + {'Open in lightbox view'}
}>
this.updateOpenDoc(activeItem)}> Lightbox
@@ -1440,22 +1336,7 @@ export class PresBox extends ViewBoxBaseComponent() {
- (this._batch = UndoManager.StartBatch('presDuration'))} - onPointerUp={() => this._batch?.end()} - onChange={(e: React.ChangeEvent) => { - e.stopPropagation(); - this.setDurationTime(e.target.value); - }} - /> + {inputter('0.1', '0.1', '20', duration, targetDoc.type !== DocumentType.AUDIO, this.setDurationTime)}
Short
Medium
@@ -1471,98 +1352,30 @@ export class PresBox extends ViewBoxBaseComponent() { className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); - this.openEffectDropdown = !this.openEffectDropdown; + this._openEffectDropdown = !this._openEffectDropdown; })} - style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + style={{ borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5, border: this._openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> {effect.toString()} - -
e.stopPropagation()}> -
e.stopPropagation()} - onClick={() => this.updateEffect(PresEffect.None)}> - None -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}> - Fade In -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}> - Flip -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}> - Rotate -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}> - Bounce -
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}> - Roll -
+ +
e.stopPropagation()}> + {presEffect(PresEffect.None)} + {presEffect(PresEffect.Fade)} + {presEffect(PresEffect.Flip)} + {presEffect(PresEffect.Rotate)} + {presEffect(PresEffect.Bounce)} + {presEffect(PresEffect.Roll)}
Effect direction
-
{this.effectDirection}
+
{StrCast(this.activeItem.presEffectDirection)}
- {'Enter from left'}
}> -
this.updateEffectDirection(PresEffect.Left)}> - -
- - {'Enter from right'}
}> -
this.updateEffectDirection(PresEffect.Right)}> - -
- - -
{'Enter from top'}
- - }> -
this.updateEffectDirection(PresEffect.Top)}> - -
-
- -
{'Enter from bottom'}
- - }> -
this.updateEffectDirection(PresEffect.Bottom)}> - -
-
- -
{'Enter from center'}
- - }> -
this.updateEffectDirection(PresEffect.Center)}>
-
+ {presDirection(PresEffect.Left, 'angle-right', 1, 2, {})} + {presDirection(PresEffect.Right, 'angle-left', 3, 2, {})} + {presDirection(PresEffect.Top, 'angle-down', 2, 1, {})} + {presDirection(PresEffect.Bottom, 'angle-up', 2, 3, {})} + {presDirection(PresEffect.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
)} @@ -1576,34 +1389,18 @@ export class PresBox extends ViewBoxBaseComponent() { } } - @computed get effectDirection() { - // prettier-ignore - switch (this.activeItem.presEffectDirection) { - case 'left': return 'Enter from left'; - case 'right': return 'Enter from right'; - case 'top': return'Enter from top'; - case 'bottom': return 'Enter from bottom'; - } - return 'Enter from center'; - } - @undoBatch @action applyTo = (array: Doc[]) => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - this.updateMovement(activeItem.presMovement, true); - this.updateEffect(activeItem.presEffect, true); - this.updateEffectDirection(activeItem.presEffectDirection, true); - array.forEach(doc => { - const curDoc = Cast(doc, Doc, null); - const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); - if (tagDoc && targetDoc) { - curDoc.presTransition = activeItem.presTransition; - curDoc.presDuration = activeItem.presDuration; - curDoc.presHideBefore = activeItem.presHideBefore; - curDoc.presHideAfter = activeItem.presHideAfter; - } + this.updateMovement(this.activeItem.presMovement as PresMovement, true); + this.updateEffect(this.activeItem.presEffect as PresEffect, true); + this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffect, true); + const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem; + array.forEach(curDoc => { + curDoc.presTransition = presTransition; + curDoc.presDuration = presDuration; + curDoc.presHideBefore = presHideBefore; + curDoc.presHideAfter = presHideAfter; }); }; @@ -1785,7 +1582,7 @@ export class PresBox extends ViewBoxBaseComponent() { return (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> @@ -1956,7 +1753,7 @@ export class PresBox extends ViewBoxBaseComponent() { // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @computed get presentDropdown() { return ( -
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
() { const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black'; const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black'; return ( -
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
{this.stringType} selected
() { @undoBatch @action switchActive = (color: ColorState) => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - const val = String(color.hex); - targetDoc['pres-text-color'] = val; + this.targetDoc['pres-text-color'] = String(color.hex); return true; }; @undoBatch @action switchPresented = (color: ColorState) => { - const targetDoc: Doc = this.targetDoc; - const val = String(color.hex); - targetDoc['pres-text-viewed-color'] = val; + this.targetDoc['pres-text-viewed-color'] = String(color.hex); return true; }; @@ -2228,12 +2020,11 @@ export class PresBox extends ViewBoxBaseComponent() { //Toggle whether the user edits or not @action editScrollProgressivize = (e: React.MouseEvent) => { - const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (!targetDoc.editScrollProgressivize) { if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; - activeItem.scrollProgressivize = true; + this.activeItem.scrollProgressivize = true; } targetDoc.editScrollProgressivize = true; } else { @@ -2245,8 +2036,7 @@ export class PresBox extends ViewBoxBaseComponent() { @action progressivizeScroll = (e: React.MouseEvent) => { e.stopPropagation(); - const activeItem: Doc = this.activeItem; - activeItem.scrollProgressivize = !activeItem.scrollProgressivize; + this.activeItem.scrollProgressivize = !this.activeItem.scrollProgressivize; const targetDoc: Doc = this.targetDoc; targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize; // CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); @@ -2353,13 +2143,14 @@ export class PresBox extends ViewBoxBaseComponent() { @action checkList = (doc: Doc, list: any): number => { const x: List = list; - if (x && x.length >= NumCast(doc._currentFrame) + 1) { + if (x?.length >= NumCast(doc._currentFrame) + 1) { return x[NumCast(doc._currentFrame)]; } else if (x) { x.length = NumCast(doc._currentFrame) + 1; x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1]; return x[NumCast(doc._currentFrame)]; - } else return 100; + } + return 100; }; @computed get progressivizeChildDocs() { @@ -2413,26 +2204,14 @@ export class PresBox extends ViewBoxBaseComponent() { } @action - nextAppearFrame = (doc: Doc, i: number): void => { - // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const appearFrame = Cast(doc.appearFrame, 'number', null); - if (appearFrame === undefined) { - doc.appearFrame = 0; - } - doc.appearFrame = appearFrame + 1; + nextAppearFrame = (doc: Doc, i: number) => { + doc.appearFrame = (Cast(doc.appearFrame, 'number', null) ?? 0) + 1; this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); }; @action - prevAppearFrame = (doc: Doc, i: number): void => { - // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const appearFrame = Cast(doc.appearFrame, 'number', null); - if (appearFrame === undefined) { - doc.appearFrame = 0; - } - doc.appearFrame = Math.max(0, appearFrame - 1); + prevAppearFrame = (doc: Doc, i: number) => { + doc.appearFrame = Math.max(0, (Cast(doc.appearFrame, 'number', null) ?? 0) - 1); this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); }; @@ -2483,12 +2262,7 @@ export class PresBox extends ViewBoxBaseComponent() {
*/} - -
{'View paths'}
- - }> + {'View paths'}
}>
1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} className={'toolbar-button'} @@ -2507,22 +2281,12 @@ export class PresBox extends ViewBoxBaseComponent() {
*/} - -
{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}
- - }> + {presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}
}>
- -
{propTitle}
- - }> + {propTitle}
}>
0 ? activeColor : inactiveColor }} />
@@ -2545,14 +2309,14 @@ export class PresBox extends ViewBoxBaseComponent() {
{isMini ? null : ( { - e.stopPropagation(); - this._scrubbing = true; - })} - onChange={(e: React.ChangeEvent) => this.setPlayheadTime(Number(e.target.value))} - onPointerUp={action((e: React.PointerEvent) => { - e.stopPropagation(); - this._scrubbing = false; - })} - /> -
- ) : ( -
/
- )} - -
{formatTime(this.timeline.clipDuration)}
-
- )} - -
- -
- - {!this._fullScreen && width > 300 && ( -
- -
- )} - - {!this._fullScreen && width > 300 && ( -
- -
- )} - -
{ - e.stopPropagation(); - this.toggleMute(); - }}> - -
- {width > 300 && ( - e.stopPropagation()} - onChange={(e: React.ChangeEvent) => this.setVolume(Number(e.target.value))} - /> - )} - - {!this._fullScreen && this.heightPercent !== 100 && width > 300 && ( - <> -
- -
- { - e.stopPropagation(); - }} - onChange={(e: React.ChangeEvent) => { - this.zoom(Number(e.target.value)); - }} - /> - - )} - - ); - } - // renders CollectionStackedTimeline @computed get renderTimeline() { return ( @@ -1149,6 +1043,112 @@ export class VideoBox extends ViewBoxAnnotatableComponent ); } + + @computed get UIButtons() { + const bounds = this.props.docViewPath().lastElement().getBounds(); + const width = (bounds?.right || 0) - (bounds?.left || 0); + const curTime = NumCast(this.layoutDoc._currentTimecode) - (this.timeline?.clipStart || 0); + return ( + <> +
+ +
+ + {this.timeline && width > 150 && ( +
+
{formatTime(curTime)}
+ + {this._fullScreen || (this.heightPercent === 100 && width > 200) ? ( +
+ { + e.stopPropagation(); + this._scrubbing = true; + })} + onChange={(e: React.ChangeEvent) => this.setPlayheadTime(Number(e.target.value))} + onPointerUp={action((e: React.PointerEvent) => { + e.stopPropagation(); + this._scrubbing = false; + })} + /> +
+ ) : ( +
/
+ )} + +
{formatTime(this.timeline.clipDuration)}
+
+ )} + +
+ +
+ + {!this._fullScreen && width > 300 && ( +
+ +
+ )} + + {!this._fullScreen && width > 300 && ( +
+ +
+ )} + +
{ + e.stopPropagation(); + this.toggleMute(); + }}> + +
+ {width > 300 && ( + e.stopPropagation()} + onChange={(e: React.ChangeEvent) => this.setVolume(Number(e.target.value))} + /> + )} + + {!this._fullScreen && this.heightPercent !== 100 && width > 300 && ( + <> +
+ +
+ { + e.stopPropagation(); + }} + onChange={(e: React.ChangeEvent) => { + this.zoom(Number(e.target.value)); + }} + /> + + )} + + ); + } } VideoBox._nativeControls = false; diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 787e331c5..0b6e18743 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -86,7 +86,7 @@ export default class UploadManager extends ApiManager { const videoId = JSON.parse(payload).videoId; const results: Upload.FileResponse[] = []; const result = await DashUploadUtils.uploadYoutube(videoId); - result && !(result.result instanceof Error) && results.push(result); + result && results.push(result); _success(res, results); resolve(); }); diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index ef7192ecc..28e26e51e 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -17,8 +17,6 @@ import { resolvedServerUrl } from './server_Initialization'; import { AcceptableMedia, Upload } from './SharedMediaTypes'; import request = require('request-promise'); import formidable = require('formidable'); -import { file } from 'jszip'; -import { csvParser } from './DataVizUtils'; const { exec } = require('child_process'); const parse = require('pdf-parse'); const ffmpeg = require('fluent-ffmpeg'); @@ -102,16 +100,41 @@ export namespace DashUploadUtils { } export function uploadYoutube(videoId: string): Promise { - console.log('UPLOAD ' + videoId); return new Promise>((res, rej) => { - exec('youtube-dl -o ' + (videoId + '.mp4') + ' https://www.youtube.com/watch?v=' + videoId + ' -f "best[filesize<50M]"', (error: any, stdout: any, stderr: any) => { - if (error) console.log(`error: ${error.message}`); - else if (stderr) console.log(`stderr: ${stderr}`); - else { - console.log(`stdout: ${stdout}`); - const data = { size: 0, path: videoId + '.mp4', name: videoId, type: 'video/mp4' }; - const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: null, length: 0, mime: '', toJson: () => undefined as any }) }; - res(MoveParsedFile(file, Directory.videos)); + console.log('Uploading YouTube video: ' + videoId); + exec('youtube-dl -o ' + (videoId + '.mp4') + ' ' + videoId + ' -f "bestvideo[filesize<5M]+bestaudio/bestvideo+bestaudio"', (error: any, stdout: any, stderr: any) => { + if (error) { + console.log(`error: Error: ${error.message}`); + res({ + source: { + size: 0, + path: videoId, + name: videoId, + type: '', + toJSON: () => ({ name: videoId, path: videoId }), + }, + result: { name: 'failed youtube query', message: `Could not upload YouTube video (${videoId}). Error: ${error.message}` }, + }); + } else if (stderr) { + console.log(`stderr: StdError: ${stderr}`); + res({ + source: { + size: 0, + path: videoId, + name: videoId, + type: '', + toJSON: () => ({ name: videoId, path: videoId }), + }, + result: { name: 'failed youtube query', message: `Could not upload YouTube video (${videoId}). Error: ${stderr}` }, + }); + } else { + exec('youtube-dl -o ' + (videoId + '.mp4') + ' ' + videoId + ' --get-duration', (error: any, stdout: any, stderr: any) => { + const time = Array.from(stdout.trim().split(':')).reverse(); + const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0); + const data = { size: 0, path: videoId + '.mp4', name: videoId, type: 'video/mp4' }; + const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: duration.toString(), mime: '', toJson: () => undefined as any }) }; + res(MoveParsedFile(file, Directory.videos)); + }); } }); }); @@ -352,7 +375,7 @@ export namespace DashUploadUtils { * @param suffix If the file doesn't have a suffix and you want to provide it one * to appear in the new location */ - export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string): Promise { + export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string, duration?: number): Promise { const { path: sourcePath } = file; let name = path.basename(sourcePath); suffix && (name += suffix); @@ -368,6 +391,7 @@ export namespace DashUploadUtils { agnostic: getAccessPaths(destination, name), }, rawText: text, + duration, }, }); }); diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index cde95526f..7db1c2dae 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -2,36 +2,45 @@ import { ExifData } from 'exif'; import { File } from 'formidable'; export namespace AcceptableMedia { - export const gifs = [".gif"]; - export const pngs = [".png"]; - export const jpgs = [".jpg", ".jpeg"]; - export const webps = [".webp"]; - export const tiffs = [".tiff"]; + export const gifs = ['.gif']; + export const pngs = ['.png']; + export const jpgs = ['.jpg', '.jpeg']; + export const webps = ['.webp']; + export const tiffs = ['.tiff']; export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; - export const videoFormats = [".mov", ".mp4", ".quicktime", ".mkv", ".x-matroska;codecs=avc1"]; - export const applicationFormats = [".pdf"]; - export const audioFormats = [".wav", ".mp3", ".mpeg", ".flac", ".au", ".aiff", ".m4a", ".webm"]; + export const videoFormats = ['.mov', '.mp4', '.quicktime', '.mkv', '.x-matroska;codecs=avc1']; + export const applicationFormats = ['.pdf']; + export const audioFormats = ['.wav', '.mp3', '.mpeg', '.flac', '.au', '.aiff', '.m4a', '.webm']; } export namespace Upload { - export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation { - return "nativeWidth" in uploadResponse; + return 'nativeWidth' in uploadResponse; + } + + export function isVideoInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.VideoInformation { + return 'duration' in uploadResponse; } export interface FileInformation { accessPaths: AccessPathInfo; rawText?: string; + duration?: number; } - export type FileResponse = { source: File, result: T | Error }; + export type FileResponse = { source: File; result: T | Error }; export type ImageInformation = FileInformation & InspectionResults; + export type VideoInformation = FileInformation & VideoResults; + export interface AccessPathInfo { - [suffix: string]: { client: string, server: string }; + [suffix: string]: { client: string; server: string }; } + export interface VideoResults { + duration: number; + } export interface InspectionResults { source: string; requestable: string; @@ -44,8 +53,7 @@ export namespace Upload { } export interface EnrichedExifData { - data: ExifData & ExifData["gps"]; + data: ExifData & ExifData['gps']; error?: string; } - -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From 2d8f763d763080fd52e940abb1a98b41e2da23fd Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 7 Sep 2022 14:28:44 -0400 Subject: fixed dragging stackedTimeline entries so that they appear during drag. fixed cursors for video/pdf/stackedTimeline to be a little more clear. fixed initial corner resize of pdfs. fixed videobox marquee drag when viewScale is undefined. --- src/client/util/DragManager.ts | 9 +++++++-- src/client/views/DocumentDecorations.tsx | 3 +++ src/client/views/MarqueeAnnotator.scss | 9 ++++----- src/client/views/MarqueeAnnotator.tsx | 3 +-- .../views/collections/CollectionStackedTimeline.scss | 4 +++- .../views/collections/CollectionStackedTimeline.tsx | 4 +++- .../collections/collectionFreeForm/MarqueeView.scss | 20 +++++++++----------- src/client/views/nodes/VideoBox.tsx | 4 ++-- 8 files changed, 32 insertions(+), 24 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index cec158d23..664933de0 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -357,10 +357,13 @@ export namespace DragManager { let rot = 0; const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : []; const dragElements = eles.map(ele => { + let useDim = false; if (ele?.parentElement?.parentElement?.parentElement?.className === 'collectionFreeFormDocumentView-container') { ele = ele.parentElement.parentElement.parentElement; const rotStr = ele.style.transform.replace(/.*rotate\(([-0-9.]*)deg\).*/, '$1'); if (rotStr) rot = Number(rotStr); + } else { + useDim = true; } if (rot < 0) rot += 360; if (!ele.parentNode) dragDiv.appendChild(ele); @@ -402,6 +405,8 @@ export namespace DragManager { xs.push(((0 - minx) / (maxx - minx)) * rect.width + rect.left); ys.push(((0 - miny) / (maxy - miny)) * rect.height + rect.top); scalings.push(scaling); + const width = useDim ? getComputedStyle(ele).width : ''; + const height = useDim ? getComputedStyle(ele).height : ''; Object.assign(dragElement.style, { opacity: '0.7', position: 'absolute', @@ -414,8 +419,8 @@ export namespace DragManager { borderRadius: getComputedStyle(ele).borderRadius, zIndex: globalCssVariables.contextMenuZindex, transformOrigin: '0 0', - width: '', - height: '', + width, + height, transform: `translate(${xs[0]}px, ${ys[0]}px) rotate(${rot}deg) scale(${scaling})`, }); dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3589e014a..a79f727a7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -476,6 +476,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions(); if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { const doc = Document(docView.rootDoc); + if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight)) { + doc._nativeHeight = (NumCast(doc._height) / NumCast(doc._width, 1)) * docView.nativeWidth; + } const nwidth = docView.nativeWidth; const nheight = docView.nativeHeight; let docheight = doc._height || 0; diff --git a/src/client/views/MarqueeAnnotator.scss b/src/client/views/MarqueeAnnotator.scss index c90d48a65..5c65f35e9 100644 --- a/src/client/views/MarqueeAnnotator.scss +++ b/src/client/views/MarqueeAnnotator.scss @@ -1,12 +1,11 @@ - .marqueeAnnotator-annotationBox { position: absolute; background-color: rgba(245, 230, 95, 0.616); } - .marqueeAnnotator-dragBox { - position:absolute; + position: absolute; background-color: transparent; - opacity: 0.1; -} \ No newline at end of file + opacity: 0.2; + cursor: default; +} diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index f90ad8bb5..d9a989309 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -271,8 +271,7 @@ export class MarqueeAnnotator extends React.Component { width: `${this._width}px`, height: `${this._height}px`, border: `${this._width === 0 ? '' : '2px dashed black'}`, - opacity: 0.2, - }}>
+ }}/> ); } } diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index aa8502c20..5a107d2ca 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -10,6 +10,7 @@ border-width: 0 2px 0 2px; &:hover { + cursor: default; .collectionStackedTimeline-hover { display: block; } @@ -109,14 +110,15 @@ height: 100%; width: 10px; pointer-events: all; - cursor: ew-resize; z-index: 100; } .collectionStackedTimeline-resizer { right: 0; + cursor: e-resize; } .collectionStackedTimeline-left-resizer { left: 0; + cursor: w-resize; } } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 2543624d3..b29abf083 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -244,6 +244,7 @@ export class CollectionStackedTimeline extends CollectionSubView DocumentManager.Instance.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); + this._timelineWrapper && (this._timelineWrapper.style.cursor = ''); }), (e, doubleTap) => { if (e.button !== 2) { @@ -561,7 +563,7 @@ export class CollectionStackedTimeline extends CollectionSubView
e.stopPropagation()} onScroll={this.setScroll} onMouseMove={e => this.isContentActive() && this.onHover(e)} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 41e4d6b6a..e0f5cbe5b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,16 +1,14 @@ - .marqueeView { position: inherit; - top:0; - left:0; - width:100%; - height:100%; + top: 0; + left: 0; + width: 100%; + height: 100%; overflow: hidden; border-radius: inherit; user-select: none; } - .marqueeView:focus-within { overflow: hidden; } @@ -22,13 +20,13 @@ border-color: black; pointer-events: none; .marquee-legend { - bottom:-18px; - left:0; + bottom: -18px; + left: 0; position: absolute; font-size: 9; - white-space:nowrap; + white-space: nowrap; } .marquee-legend::after { - content: "Press for lasso" + content: 'Press for lasso'; } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index bfb8c1528..5a3594ffc 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -551,7 +551,7 @@ export class VideoBox extends ViewBoxAnnotatableComponentLoading
) : ( -
+
this._fullScreen && e.stopPropagation()}> {this._fullScreen && (
{ - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) { + if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._viewScale, 1) === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) { setupMoveUpEvents( this, e, -- cgit v1.2.3-70-g09d2 From 71f9850387c2719ed5b050db4130fa15852cb38d Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 8 Sep 2022 13:30:36 -0400 Subject: fixed pinning viewScale --- src/client/views/nodes/trails/PresBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 50a77ce57..1439ae5d5 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -420,7 +420,7 @@ export class PresBox extends ViewBoxBaseComponent() { const panY = NumCast(pinDoc._panY); const pw = NumCast(pinProps.panelWidth); const ph = NumCast(pinProps.panelHeight); - const ps = NumCast(pinDoc._viewScale); + const ps = NumCast(pinDoc._viewScale, 1); if (pw && ph && ps) { pinDoc.presPinViewBounds = new List([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]); } -- cgit v1.2.3-70-g09d2 From 0454279c9845fc64dc2c77aeb6bca9a5fee573f6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 8 Sep 2022 13:34:55 -0400 Subject: added rotation to preselement saved parameters. --- src/client/views/nodes/trails/PresBox.tsx | 2 ++ src/client/views/nodes/trails/PresElementBox.tsx | 1 + 2 files changed, 3 insertions(+) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 1439ae5d5..beebca973 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -407,6 +407,7 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || textview || pinProps.activeFrame !== undefined; pinDoc.presX = NumCast(targetDoc.x); pinDoc.presY = NumCast(targetDoc.y); + pinDoc.presRot = NumCast(targetDoc.jitterRotation); pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); @@ -487,6 +488,7 @@ export class PresBox extends ViewBoxBaseComponent() { targetDoc._dataTransition = presTransitionTime; targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x)); targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y)); + targetDoc.jitterRotation = NumCast(activeItem.presRot, NumCast(targetDoc.jitterRotation)); targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width)); targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height)); setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index e4fab7528..d88ff45fa 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -337,6 +337,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { activeItem.presX = NumCast(targetDoc.x); activeItem.presY = NumCast(targetDoc.y); + activeItem.presRot = NumCast(targetDoc.jitterRotation); activeItem.presWidth = NumCast(targetDoc.width); activeItem.presHeight = NumCast(targetDoc.height); }; -- cgit v1.2.3-70-g09d2 From 637dd4dd36a74e49c653aa6e265a0db41797de00 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 8 Sep 2022 13:43:02 -0400 Subject: made presbox slide changes undoable --- src/client/views/nodes/trails/PresBox.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index beebca973..847617378 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -312,6 +312,7 @@ export class PresBox extends ViewBoxBaseComponent() { //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. + @undoBatch public gotoDocument = action((index: number, from?: Doc, group?: boolean) => { Doc.UnBrushAllDocs(); -- cgit v1.2.3-70-g09d2 From 2c5942d76ad6e9b5874b98658b7c5af59cdfa367 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 8 Sep 2022 14:38:38 -0400 Subject: fixed font menu settings to be accurate of current selection. --- src/client/documents/Documents.ts | 16 ++++-- src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/nodes/button/FontIconBox.tsx | 36 ++++--------- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- .../views/nodes/formattedText/RichTextMenu.tsx | 62 +++++++++------------- 5 files changed, 49 insertions(+), 73 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7111cb233..57a24b304 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1386,11 +1386,17 @@ export namespace DocUtils { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript( - scripts[key], - { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name }, - { _readOnly_: true } - ); + doc[key] = ScriptField.MakeScript(scripts[key], { + dragData: DragManager.DocumentDragData.name, + value: 'any', + _readOnly_: 'boolean', + scriptContext: 'any', + thisContainer: Doc.name, + documentView: Doc.name, + heading: Doc.name, + checked: 'boolean', + containingTreeView: Doc.name, + }); } }); funcs && diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 25852c6ce..9d3f19e50 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -623,8 +623,8 @@ export class CurrentUserUtils { return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'}, btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, - { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, - { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_); }'}}, + { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, + { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_);}'}}, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} }, diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index c72b5ca9b..4a6099fb3 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -576,27 +576,19 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { // toggle: Set overlay status of selected document ScriptingGlobals.add(function setFont(font: string, checkResult?: boolean) { - SelectionManager.Docs().map(doc => (doc._fontFamily = font)); - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (checkResult) { - return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - } - if (editorView) RichTextMenu.Instance.setFontFamily(font); - else Doc.UserDoc().fontFamily = font; + if (checkResult) return RichTextMenu.Instance?.fontFamily; + font && RichTextMenu.Instance.setFontFamily(font); }); ScriptingGlobals.add(function getActiveTextInfo(info: 'family' | 'size' | 'color' | 'highlight') { const editorView = RichTextMenu.Instance.TextView?.EditorView; const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection(); + // prettier-ignore switch (info) { - case 'family': - return style?.activeFamilies[0]; - case 'size': - return style?.activeSizes[0]; - case 'color': - return style?.activeColors[0]; - case 'highlight': - return style?.activeHighlights[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]; } }); @@ -621,14 +613,8 @@ ScriptingGlobals.add(function setBulletList(mapStyle: 'bullet' | 'decimal', chec // toggle: Set overlay status of selected document ScriptingGlobals.add(function setFontColor(color?: string, checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - - if (checkResult) { - return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor; - } - - if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch); - else Doc.UserDoc().fontColor = color; + if (checkResult) return RichTextMenu.Instance.fontColor; + color && RichTextMenu.Instance.setColor(color); }); // toggle: Set overlay status of selected document @@ -650,14 +636,12 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo // toggle: Set overlay status of selected document ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return RichTextMenu.Instance?.fontSize.replace('px', ''); } if (typeof size === 'number') size = size.toString(); if (size && Number(size).toString() === size) size += 'px'; - if (editorView) RichTextMenu.Instance.setFontSize(size); - else Doc.UserDoc()._fontSize = size; + RichTextMenu.Instance.setFontSize(size); }); ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 81ac45521..314696251 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1387,10 +1387,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent mark.type === schema.marks.user_mark)) { + if (this._editorView) { 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?.some(mark => mark.type === schema.marks.user_mark) ? [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)] : []), diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 0cbe60c0c..42a204e1d 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -64,6 +64,7 @@ export class RichTextMenu extends AntimodeMenu { super(props); runInAction(() => { RichTextMenu.Instance = this; + this.updateMenu(undefined, undefined, props); this._canFade = false; this.Pinned = true; }); @@ -103,13 +104,12 @@ export class RichTextMenu extends AntimodeMenu { return; } this.view = view; - if (!view || !view.hasFocus()) { - return; - } props && (this.editorProps = props); // Don't do anything if the document/selection didn't change - if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return; + if (view && view.hasFocus()) { + if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return; + } // update active marks const activeMarks = this.getActiveMarksOnSelection(); @@ -124,9 +124,9 @@ 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 ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; - this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...'; + this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; + this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; // update link in current selection @@ -279,28 +279,15 @@ export class RichTextMenu extends AntimodeMenu { this._superscriptActive = false; activeMarks.forEach(mark => { + // prettier-ignore switch (mark.name) { - case 'noAutoLinkAnchor': - this._noLinkActive = 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; + case 'noAutoLinkAnchor': this._noLinkActive = 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; } }); } @@ -342,14 +329,13 @@ export class RichTextMenu extends AntimodeMenu { if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { this.TextView.dataDoc.fontSize = fontSize; this.view.focus(); - this.updateMenu(this.view, undefined, this.props); } else { 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); } - } + } else Doc.UserDoc()._fontSize = fontSize; + this.updateMenu(this.view, undefined, this.props); }; setFontFamily = (family: string) => { @@ -357,8 +343,8 @@ export class RichTextMenu extends AntimodeMenu { 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); - } + } else Doc.UserDoc()._fontFamily = family; + this.updateMenu(this.view, undefined, this.props); }; setHighlight(color: String, view: EditorView, dispatch: any) { @@ -368,13 +354,13 @@ export class RichTextMenu extends AntimodeMenu { this.setMark(highlightMark, view.state, dispatch, false); } - setColor(color: String, view: EditorView, dispatch: any) { + setColor(color: string) { if (this.view) { - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color }); + const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color }); this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true); - view.focus(); - this.updateMenu(this.view, undefined, this.props); - } + this.view.focus(); + } else Doc.UserDoc().fontColor = color; + this.updateMenu(this.view, undefined, this.props); } // TODO: remove doesn't work -- cgit v1.2.3-70-g09d2 From d8753c8de6cc9ef9d4bfdb87f11605f0ef80929c Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 8 Sep 2022 17:30:17 -0400 Subject: fixed maintaining selections after ctrl-a + setting a font property --- src/client/views/nodes/formattedText/RichTextMenu.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 42a204e1d..c548e211b 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -144,13 +144,8 @@ export class RichTextMenu extends AntimodeMenu { 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)))); } else if (dontToggle) { - toggleMark(mark.type, mark.attrs)(state, (tx: any) => { - const { from, $from, to, empty } = tx.selection; - if (!tx.doc.rangeHasMark(from, to, mark.type)) { - // hack -- should have just set the mark in the first place - toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); - } else dispatch(tx); - }); + const tr = state.tr.addMark(state.selection.from, state.selection.to, mark); + dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise } else { toggleMark(mark.type, mark.attrs)(state, dispatch); } @@ -198,7 +193,8 @@ export class RichTextMenu extends AntimodeMenu { const state = this.view.state; const pos = this.view.state.selection.$from; const marks: Mark[] = [...(state.storedMarks ?? [])]; - if (state.selection.empty) { + if (state.storedMarks !== null) { + } else if (state.selection.empty) { const ref_node = this.reference_node(pos); marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : [])); } else { -- cgit v1.2.3-70-g09d2 From 5e0ababf6d323ff599fa469693d5a6b20e438baf Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 11 Sep 2022 13:18:35 -0400 Subject: fixed crash when selecting ink strokes --- src/client/views/nodes/formattedText/RichTextMenu.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index c548e211b..9faaa0f3a 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -154,7 +154,7 @@ export class RichTextMenu extends AntimodeMenu { // finds font sizes and families in selection getActiveAlignment() { - if (this.view && this.TextView.props.isSelected(true)) { + 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) { @@ -167,7 +167,7 @@ export class RichTextMenu extends AntimodeMenu { // finds font sizes and families in selection getActiveListStyle() { - if (this.view && this.TextView.props.isSelected(true)) { + if (this.view && this.TextView?.props.isSelected(true)) { const path = (this.view.state.selection.$from as any).path; for (let i = 0; i < path.length; i += 3) { if (path[i].type === this.view.state.schema.nodes.ordered_list) { @@ -189,7 +189,7 @@ export class RichTextMenu extends AntimodeMenu { const activeSizes: string[] = []; const activeColors: string[] = []; const activeHighlights: string[] = []; - if (this.TextView.props.isSelected(true)) { + if (this.TextView?.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; const marks: Mark[] = [...(state.storedMarks ?? [])]; @@ -222,7 +222,7 @@ export class RichTextMenu extends AntimodeMenu { //finds all active marks on selection in given group getActiveMarksOnSelection() { let activeMarks: MarkType[] = []; - if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks; + if (!this.view || !this.TextView?.props.isSelected(true)) return activeMarks; const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); @@ -412,7 +412,7 @@ export class RichTextMenu extends AntimodeMenu { } align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { - if (this.TextView.props.isSelected(true)) { + if (this.TextView?.props.isSelected(true)) { var tr = view.state.tr; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => { if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) { -- cgit v1.2.3-70-g09d2 From a0b595c00111404e9a4fb6b9a926ef22c6e2979b Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 11 Sep 2022 13:27:44 -0400 Subject: fixed fontFamily menu for ink strokes --- .../views/nodes/formattedText/RichTextMenu.tsx | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 9faaa0f3a..6c6d26af5 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -183,13 +183,11 @@ export class RichTextMenu extends AntimodeMenu { // finds font sizes and families in selection getActiveFontStylesOnSelection() { - if (!this.view) return { activeFamilies: [], activeSizes: [], activeColors: [], activeHighlights: [] }; - - const activeFamilies: string[] = []; - const activeSizes: string[] = []; - const activeColors: string[] = []; - const activeHighlights: string[] = []; - if (this.TextView?.props.isSelected(true)) { + const activeFamilies = new Set(); + const activeSizes = new Set(); + const activeColors = new Set(); + const activeHighlights = new Set(); + if (this.view && this.TextView?.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; const marks: Mark[] = [...(state.storedMarks ?? [])]; @@ -203,13 +201,13 @@ export class RichTextMenu extends AntimodeMenu { }); } 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)); + m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family); + m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color); + m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize); + m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight)); }); } - return { activeFamilies, activeSizes, activeColors, activeHighlights }; + return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) }; } getMarksInSelection(state: EditorState) { -- cgit v1.2.3-70-g09d2 From f847c6d554f9dcecbd6c3024f712510f341daf67 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Sep 2022 11:36:15 -0400 Subject: made annotation pen available in lightbox. --- src/client/views/GestureOverlay.scss | 5 +- src/client/views/GestureOverlay.tsx | 43 +++++----- src/client/views/LightboxView.scss | 80 ++++++++++++------- src/client/views/LightboxView.tsx | 101 ++++++++++++++---------- src/client/views/MainView.tsx | 48 +++++------ src/client/views/collections/CollectionMenu.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 9 +-- 7 files changed, 159 insertions(+), 128 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index f5cbbffb1..bfe2d5c64 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -1,8 +1,9 @@ .gestureOverlay-cont { - width: 100vw; - height: 100vh; + width: 100%; + height: 100%; position: absolute; touch-action: none; + top: 0; .pointerBubbles { width: 100%; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 6f28ef9eb..850688e7e 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,7 +1,6 @@ import React = require('react'); import * as fitCurve from 'fit-curve'; -import { action, computed, observable, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; +import { action, computed, observable, runInAction, trace } from 'mobx'; import { Doc } from '../../fields/Doc'; import { InkData, InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; @@ -41,9 +40,13 @@ import { RadialMenu } from './nodes/RadialMenu'; import HorizontalPalette from './Palette'; import { Touchable } from './Touchable'; import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu'; +import { observer } from 'mobx-react'; +interface GestureOverlayProps { + isActive: boolean; +} @observer -export class GestureOverlay extends Touchable { +export class GestureOverlay extends Touchable { static Instance: GestureOverlay; @observable public InkShape: string = ''; @@ -83,7 +86,7 @@ export class GestureOverlay extends Touchable { protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; - constructor(props: Readonly<{}>) { + constructor(props: any) { super(props); GestureOverlay.Instance = this; @@ -495,7 +498,7 @@ export class GestureOverlay extends Touchable { } this._strokes = []; - this._points = []; + this._points.length = 0; this._possibilities = []; document.removeEventListener('touchend', this.handleHandUp); } @@ -619,14 +622,14 @@ export class GestureOverlay extends Touchable { // get the two targets at the ends of the line const ep1 = this._points[0]; - const ep2 = this._points[this._points.length - 1]; + const ep2 = this._points.lastElement(); const target1 = document.elementFromPoint(ep1.X, ep1.Y); const target2 = document.elementFromPoint(ep2.X, ep2.Y); const ge = new CustomEvent('dashOnGesture', { bubbles: true, detail: { - points: this._points, + points: this._points.slice(), gesture: GestureUtils.Gestures.Line, bounds: B, }, @@ -652,8 +655,8 @@ export class GestureOverlay extends Touchable { if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { switch (this.Tool) { case ToolglassTools.InkToText: - this._strokes.push(new Array(...this._points)); - this._points = []; + this._strokes.push(this._points.slice()); + this._points.length = 0; CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then(results => { const wordResults = results.filter((r: any) => r.category === 'line'); const possibilities: string[] = []; @@ -675,7 +678,7 @@ export class GestureOverlay extends Touchable { break; case ToolglassTools.IgnoreGesture: this.dispatchGesture(GestureUtils.Gestures.Stroke); - this._points = []; + this._points.length = 0; break; } } @@ -683,7 +686,7 @@ export class GestureOverlay extends Touchable { else if (this.InkShape) { this.makePolygon(this.InkShape, false); this.dispatchGesture(GestureUtils.Gestures.Stroke); - this._points = []; + this._points.length = 0; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { this.InkShape = ''; Doc.ActiveTool = InkTool.None; @@ -743,15 +746,16 @@ export class GestureOverlay extends Touchable { (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._points.length = 0; + this._points.push(...controlPoints); this.dispatchGesture(GestureUtils.Gestures.Stroke); // TODO: nda - check inks to group here checkInksToGroup(); } - this._points = []; + this._points.length = 0; } } else { - this._points = []; + this._points.length = 0; } CollectionFreeFormViewChrome.Instance?.primCreated(); }; @@ -803,7 +807,7 @@ export class GestureOverlay extends Touchable { } } } - this._points = []; + this._points.length = 0; switch (shape) { //must push an extra point in the end so InteractionUtils knows pointer is up. //must be (points[0].X,points[0]-1) @@ -922,7 +926,7 @@ export class GestureOverlay extends Touchable { new CustomEvent('dashOnGesture', { bubbles: true, detail: { - points: stroke ?? this._points, + points: stroke ?? this._points.slice(), gesture: gesture as any, bounds: this.getBounds(stroke ?? this._points), text: data, @@ -1067,8 +1071,8 @@ export class GestureOverlay extends Touchable { render() { return ( -
- {this.showMobileInkOverlay ? : <>} +
+ {this.showMobileInkOverlay ? : null} {this.elements}
+ }} + />
); diff --git a/src/client/views/LightboxView.scss b/src/client/views/LightboxView.scss index 5d42cd97f..ae5dc9902 100644 --- a/src/client/views/LightboxView.scss +++ b/src/client/views/LightboxView.scss @@ -1,35 +1,55 @@ - - .lightboxView-navBtn { - margin: auto; - position: absolute; - right: 10; - top: 10; - background: transparent; - border-radius: 8; - color:white; - opacity: 0.7; - width: 35; - &:hover { - opacity: 1; - } +.lightboxView-navBtn { + margin: auto; + position: absolute; + right: 10; + top: 10; + background: transparent; + border-radius: 8; + color: white; + opacity: 0.7; + width: 25; + flex-direction: column; + display: flex; + &:hover { + opacity: 1; } - .lightboxView-tabBtn { - margin: auto; - position: absolute; - right: 35; - top: 10; - background: transparent; - border-radius: 8; - color:white; - opacity: 0.7; - width: 35; - &:hover { - opacity: 1; - } +} +.lightboxView-tabBtn { + margin: auto; + position: absolute; + right: 38; + top: 10; + background: transparent; + border-radius: 8; + color: white; + opacity: 0.7; + width: 25; + flex-direction: column; + display: flex; + &:hover { + opacity: 1; + } +} +.lightboxView-penBtn { + margin: auto; + position: absolute; + right: 70; + top: 10; + background: transparent; + border-radius: 8; + color: white; + opacity: 0.7; + width: 25; + flex-direction: column; + display: flex; + &:hover { + opacity: 1; } +} .lightboxView-frame { - position: absolute; - top: 0; left: 0; + position: absolute; + top: 0; + left: 0; width: 100%; height: 100%; background: #000000bb; @@ -51,4 +71,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 25354a09d..5613e82fb 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -4,6 +4,7 @@ import { observer } from 'mobx-react'; import 'normalize.css'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { InkTool } from '../../fields/InkField'; import { Cast, NumCast, StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils'; import { DocUtils } from '../documents/Documents'; @@ -14,6 +15,7 @@ import { Transform } from '../util/Transform'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; import { TabDocView } from './collections/TabDocView'; +import { GestureOverlay } from './GestureOverlay'; import './LightboxView.scss'; import { DocumentView } from './nodes/DocumentView'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; @@ -267,45 +269,48 @@ export class LightboxView extends React.Component { clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`, }}> {/* TODO:glr This is where it would go*/} - { - LightboxView._docView = r !== null ? r : undefined; - r && - setTimeout( - action(() => { - const target = LightboxView._docTarget; - const doc = LightboxView._doc; - const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); - if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); - //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button - }) - ); - })} - Document={LightboxView.LightboxDoc} - DataDoc={undefined} - LayoutTemplate={LightboxView.LightboxDocTemplate} - addDocument={undefined} - isDocumentActive={returnFalse} - isContentActive={returnTrue} - addDocTab={this.addDocTab} - pinToPres={TabDocView.PinDoc} - rootSelected={returnTrue} - docViewPath={returnEmptyDoclist} - docFilters={this.docFilters} - removeDocument={undefined} - styleProvider={DefaultStyleProvider} - ScreenToLocalTransform={this.lightboxScreenToLocal} - PanelWidth={this.lightboxWidth} - PanelHeight={this.lightboxHeight} - focus={DocUtils.DefaultFocus} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - renderDepth={0} - /> + + + { + LightboxView._docView = r !== null ? r : undefined; + r && + setTimeout( + action(() => { + const target = LightboxView._docTarget; + const doc = LightboxView._doc; + const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); + if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); + //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button + }) + ); + })} + Document={LightboxView.LightboxDoc} + DataDoc={undefined} + LayoutTemplate={LightboxView.LightboxDocTemplate} + addDocument={undefined} + isDocumentActive={returnFalse} + isContentActive={returnTrue} + addDocTab={this.addDocTab} + pinToPres={TabDocView.PinDoc} + rootSelected={returnTrue} + docViewPath={returnEmptyDoclist} + docFilters={this.docFilters} + removeDocument={undefined} + styleProvider={DefaultStyleProvider} + ScreenToLocalTransform={this.lightboxScreenToLocal} + PanelWidth={this.lightboxWidth} + PanelHeight={this.lightboxHeight} + focus={DocUtils.DefaultFocus} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + renderDepth={0} + /> +
{this.navBtn( @@ -332,6 +337,15 @@ export class LightboxView extends React.Component { this.future()?.length.toString() )} +
{ + e.stopPropagation(); + LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; + }}> + +
{
{ e.stopPropagation(); - LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; + Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; }}> - +
); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 45281ed69..d7b526d22 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -86,7 +86,7 @@ export class MainView extends React.Component { return this._hideUI ? 0 : 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @computed private get topOfDashUI() { - return this._hideUI ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', '')); + return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', '')); } @computed private get topOfHeaderBarDoc() { return this.topOfDashUI; @@ -610,7 +610,7 @@ export class MainView extends React.Component { @computed get dockingContent() { return ( - +
1 ? locationFields[1] : ''; if (doc.dockingConfig) return DashboardView.openDashboard(doc); + // prettier-ignore switch (locationFields[0]) { - case 'dashboard': - return DashboardView.openDashboard(doc); - case 'close': - return CollectionDockingView.CloseSplit(doc, locationParams); - case 'fullScreen': - return CollectionDockingView.OpenFullScreen(doc); - case 'lightbox': - return LightboxView.AddDocTab(doc, location); - case 'toggle': - return CollectionDockingView.ToggleSplit(doc, locationParams); - case 'inPlace': - case 'add': default: - return CollectionDockingView.AddSplit(doc, locationParams); + case 'inPlace': + case 'add': return CollectionDockingView.AddSplit(doc, locationParams); + case 'dashboard': return DashboardView.openDashboard(doc); + case 'close': return CollectionDockingView.CloseSplit(doc, locationParams); + case 'fullScreen': return CollectionDockingView.OpenFullScreen(doc); + case 'lightbox': return LightboxView.AddDocTab(doc, location); + case 'toggle': return CollectionDockingView.ToggleSplit(doc, locationParams); } }; @@ -716,7 +711,7 @@ export class MainView extends React.Component { @computed get leftMenuPanel() { return ( -
+
: null} {((page: string) => { + // prettier-ignore switch (page) { - case 'dashboard': default: - return ( - <> -
- -
- {this.mainDashboardArea} - - ); - case 'home': - return ; + case 'dashboard': return (<> +
+ +
+ {this.mainDashboardArea} + ); + case 'home': return ; } })(Doc.ActivePage)} @@ -1020,7 +1012,7 @@ export class MainView extends React.Component { {this.snapLines}
- +
); } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 6a0f69359..46e8494ab 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -34,7 +34,6 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum import { DocumentView } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { PresBox } from '../nodes/trails/PresBox'; import { DefaultStyleProvider } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionLinearView } from './collectionLinear'; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 113574a64..e628c2e44 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,8 +1,9 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; +import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -10,7 +11,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; @@ -20,6 +21,7 @@ import { DocServer } from '../../DocServer'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; +import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { InteractionUtils } from '../../util/InteractionUtils'; @@ -37,7 +39,6 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from '../DocComponent'; import { EditableView } from '../EditableView'; -import { InkingStroke } from '../InkingStroke'; import { LightboxView } from '../LightboxView'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; @@ -52,8 +53,6 @@ import { RadialMenu } from './RadialMenu'; import { ScriptingBox } from './ScriptingBox'; import { PresBox } from './trails/PresBox'; import React = require('react'); -import { DictationManager } from '../../util/DictationManager'; -import { Tooltip } from '@material-ui/core'; const { Howl } = require('howler'); interface Window { -- cgit v1.2.3-70-g09d2 From 13e0ac912beeab64a859b3463953774f3f1676f1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Sep 2022 12:09:03 -0400 Subject: fixed h1 style for use in text boxes with #,## etc markdown. made %[tix!] text tags reset user_mark to current time. --- src/client/views/DictationOverlay.tsx | 60 ++++++++++-------- src/client/views/LightboxView.tsx | 1 - src/client/views/MainView.scss | 5 ++ src/client/views/MainView.tsx | 1 + src/client/views/PreviewCursor.scss | 5 +- src/client/views/PreviewCursor.tsx | 3 +- .../views/nodes/formattedText/RichTextRules.ts | 10 ++- src/debug/Viewer.tsx | 72 +++++++++++----------- 8 files changed, 86 insertions(+), 71 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx index f4f96da8a..0bdcdc303 100644 --- a/src/client/views/DictationOverlay.tsx +++ b/src/client/views/DictationOverlay.tsx @@ -1,9 +1,8 @@ import { computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import "normalize.css"; import * as React from 'react'; import { DictationManager } from '../util/DictationManager'; -import "./Main.scss"; +import './Main.scss'; import { MainViewModal } from './MainViewModal'; @observer @@ -29,44 +28,53 @@ export class DictationOverlay extends React.Component { this.dictationOverlayVisible = false; this.dictationSuccess = undefined; DictationOverlay.Instance.hasActiveModal = false; - setTimeout(() => this.dictatedPhrase = DictationManager.placeholder, 500); + setTimeout(() => (this.dictatedPhrase = DictationManager.placeholder), 500); }, duration); - } + }; public cancelDictationFade = () => { if (this.overlayTimeout) { clearTimeout(this.overlayTimeout); this.overlayTimeout = undefined; } - } + }; - @computed public get dictatedPhrase() { return this._dictationState; } - @computed public get dictationSuccess() { return this._dictationSuccessState; } - @computed public get dictationOverlayVisible() { return this._dictationDisplayState; } - @computed public get isListening() { return this._dictationListeningState; } + @computed public get dictatedPhrase() { + return this._dictationState; + } + @computed public get dictationSuccess() { + return this._dictationSuccessState; + } + @computed public get dictationOverlayVisible() { + return this._dictationDisplayState; + } + @computed public get isListening() { + return this._dictationListeningState; + } - public set dictatedPhrase(value: string) { runInAction(() => this._dictationState = value); } - public set dictationSuccess(value: boolean | undefined) { runInAction(() => this._dictationSuccessState = value); } - public set dictationOverlayVisible(value: boolean) { runInAction(() => this._dictationDisplayState = value); } - public set isListening(value: DictationManager.Controls.ListeningUIStatus) { runInAction(() => this._dictationListeningState = value); } + public set dictatedPhrase(value: string) { + runInAction(() => (this._dictationState = value)); + } + public set dictationSuccess(value: boolean | undefined) { + runInAction(() => (this._dictationSuccessState = value)); + } + public set dictationOverlayVisible(value: boolean) { + runInAction(() => (this._dictationDisplayState = value)); + } + public set isListening(value: DictationManager.Controls.ListeningUIStatus) { + runInAction(() => (this._dictationListeningState = value)); + } render() { const success = this.dictationSuccess; const result = this.isListening && !this.isListening.interim ? DictationManager.placeholder : `"${this.dictatedPhrase}"`; const dialogueBoxStyle = { - background: success === undefined ? "gainsboro" : success ? "lawngreen" : "red", - borderColor: this.isListening ? "red" : "black", - fontStyle: "italic" + background: success === undefined ? 'gainsboro' : success ? 'lawngreen' : 'red', + borderColor: this.isListening ? 'red' : 'black', + fontStyle: 'italic', }; const overlayStyle = { - backgroundColor: this.isListening ? "red" : "darkslategrey" + backgroundColor: this.isListening ? 'red' : 'darkslategrey', }; - return (); + return ; } -} \ No newline at end of file +} diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 5613e82fb..cb5094f4b 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -1,7 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; -import 'normalize.css'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { InkTool } from '../../fields/InkField'; diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index da79d2992..c5ac1cf52 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -1,5 +1,10 @@ @import 'global/globalCssVariables'; @import 'nodeModuleOverrides'; +h1, +.h1 { + // reverts change to h1 made by normalize.css + font-size: 36px; +} .dash-tooltip { font-size: 11px; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d7b526d22..24dae8816 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -5,6 +5,7 @@ import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import 'normalize.css'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss index 60b7d14a0..7be765ea9 100644 --- a/src/client/views/PreviewCursor.scss +++ b/src/client/views/PreviewCursor.scss @@ -1,11 +1,10 @@ - .previewCursor-Dark, .previewCursor { color: black; position: absolute; transform-origin: left top; top: 0; - left:0; + left: 0; pointer-events: none; opacity: 1; z-index: 1001; @@ -13,4 +12,4 @@ .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 4c17d5a97..d56d2a310 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -1,10 +1,9 @@ import { action, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import 'normalize.css'; import * as React from 'react'; import { Doc } from '../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../fields/Types'; -import { emptyFunction, returnFalse } from '../../Utils'; +import { returnFalse } from '../../Utils'; import { DocServer } from '../DocServer'; import { Docs, DocumentOptions, DocUtils } from '../documents/Documents'; import { Transform } from '../util/Transform'; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 2097b321f..2eb62c38d 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -343,8 +343,14 @@ export class RichTextRules { const node = (state.doc.resolve(start) as any).nodeAfter; if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) { + } + return node + ? state.tr + .removeMark(start, end, schema.marks.user_mark) + .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })) + .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) + : state.tr; }), new InputRule(new RegExp(/%\(/), (state, match, start, end) => { diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx index ee7dd1fc1..02038c426 100644 --- a/src/debug/Viewer.tsx +++ b/src/debug/Viewer.tsx @@ -1,5 +1,4 @@ import { action, configure, observable, ObservableMap, Lambda } from 'mobx'; -import "normalize.css"; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { observer } from 'mobx-react'; @@ -21,7 +20,6 @@ URLField; ScriptField; CursorField; - function applyToDoc(doc: { [index: string]: FieldResult }, key: string, scriptString: string): boolean; function applyToDoc(doc: { [index: number]: FieldResult }, key: number, scriptString: string): boolean; function applyToDoc(doc: any, key: string | number, scriptString: string): boolean { @@ -37,11 +35,11 @@ function applyToDoc(doc: any, key: string | number, scriptString: string): boole } configure({ - enforceActions: "observed" + enforceActions: 'observed', }); @observer -class ListViewer extends React.Component<{ field: List }>{ +class ListViewer extends React.Component<{ field: List }> { @observable expanded = false; @@ -49,14 +47,16 @@ class ListViewer extends React.Component<{ field: List }>{ onClick = (e: React.MouseEvent) => { this.expanded = !this.expanded; e.stopPropagation(); - } + }; render() { let content; if (this.expanded) { content = (
- {this.props.field.map((field, index) => applyToDoc(this.props.field, index, value)} />)} + {this.props.field.map((field, index) => ( + applyToDoc(this.props.field, index, value)} /> + ))}
); } else { @@ -66,7 +66,7 @@ class ListViewer extends React.Component<{ field: List }>{
{content} -
+
); } } @@ -80,7 +80,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> { onClick = (e: React.MouseEvent) => { this.expanded = !this.expanded; e.stopPropagation(); - } + }; render() { let content; @@ -96,10 +96,7 @@ class DocumentViewer extends React.Component<{ field: Doc }> { }); content = (
- Document ({this.props.field[Id]}) -
- {fields} -
+ Document ({this.props.field[Id]})
{fields}
); } else { @@ -109,24 +106,23 @@ class DocumentViewer extends React.Component<{ field: Doc }> {
{content} -
+
); } } @observer -class DebugViewer extends React.Component<{ field: FieldResult, setValue(value: string): boolean }> { - +class DebugViewer extends React.Component<{ field: FieldResult; setValue(value: string): boolean }> { render() { let content; const field = this.props.field; if (field instanceof List) { - content = (); + content = ; } else if (field instanceof Doc) { - content = (); - } else if (typeof field === "string") { + content = ; + } else if (typeof field === 'string') { content =

"{field}"

; - } else if (typeof field === "number" || typeof field === "boolean") { + } else if (typeof field === 'number' || typeof field === 'boolean') { content =

{field}

; } else if (field instanceof RichTextField) { content =

RTF: {field.Data}

; @@ -153,28 +149,30 @@ class Viewer extends React.Component { @action inputOnChange = (e: React.ChangeEvent) => { this.idToAdd = e.target.value; - } + }; @action onKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - DocServer.GetRefField(this.idToAdd).then(action((field: any) => { - if (field !== undefined) { - this.fields.push(field); - } - })); - this.idToAdd = ""; + if (e.key === 'Enter') { + DocServer.GetRefField(this.idToAdd).then( + action((field: any) => { + if (field !== undefined) { + this.fields.push(field); + } + }) + ); + this.idToAdd = ''; } - } + }; render() { return ( <> - +
- {this.fields.map((field, index) => false}>)} + {this.fields.map((field, index) => ( + false}> + ))}
); @@ -182,11 +180,11 @@ class Viewer extends React.Component { } (async function () { - await DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "viewer"); - ReactDOM.render(( -
+ await DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, 'viewer'); + ReactDOM.render( +
-
), +
, document.getElementById('root') ); -})(); \ No newline at end of file +})(); -- cgit v1.2.3-70-g09d2 From 51a4c0978cdc65a4a16bc9f03c7e4eff551769af Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Sep 2022 13:12:39 -0400 Subject: fixed autosizing text with header tags. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 314696251..5cb805f80 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1697,7 +1697,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent p + Number(getComputedStyle(child).height.replace('px', '')), margins); + const proseHeight = !this.ProseRef + ? 0 + : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')) + Number(getComputedStyle(child).marginTop.replace('px', '')) + Number(getComputedStyle(child).marginBottom.replace('px', '')), margins); const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); if (this.props.setHeight && 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 -- cgit v1.2.3-70-g09d2 From 4315a0378bc54ae9eaa684d416839f635c38e865 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 12 Sep 2022 16:18:42 -0400 Subject: fixed rotating UI for video boxes. fixed generating error response for unsupported video/audio formats. --- src/client/views/DocumentDecorations.tsx | 8 +++---- src/client/views/nodes/VideoBox.tsx | 38 +++++++++++++++++++++----------- src/server/DashUploadUtils.ts | 3 +++ 3 files changed, 31 insertions(+), 18 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index a79f727a7..832d0a35c 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -738,11 +738,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P )} - {useRotation && ( -
e.preventDefault()}> - {'⟲'} -
- )} +
e.preventDefault()}> + {'⟲'} +
{useRounding && (
{ - const bounds = this.props.docViewPath().lastElement().getBounds(); - const left = bounds?.left || 0; - const right = bounds?.right || 0; - const top = bounds?.top || 0; - const height = (bounds?.bottom || 0) - top; - const width = Math.max(right - left, 100); - const uiHeight = Math.max(25, Math.min(50, height / 10)); - const uiMargin = Math.min(10, height / 20); - const vidHeight = (height * this.heightPercent) / 100; - const yPos = top + vidHeight - uiHeight - uiMargin; - const xPos = uiHeight / vidHeight > 0.4 ? right + 10 : left + 10; + const xf = this.props.ScreenToLocalTransform().inverse(); + const height = this.props.PanelHeight(); + const vidHeight = (height * this.heightPercent) / 100 / this.scaling(); + const vidWidth = this.props.PanelWidth() / this.scaling(); + const uiHeight = 25; + const uiMargin = 10; + const yBot = xf.transformPoint(0, vidHeight)[1]; + // prettier-ignore + const yMid = (xf.transformPoint(0, 0)[1] + + xf.transformPoint(0, height / this.scaling())[1]) / 2; + const xPos = xf.transformPoint(vidWidth / 2, 0)[0]; + const xRight = xf.transformPoint(vidWidth, 0)[0]; const opacity = this._scrubbing ? 0.3 : this._controlsVisible ? 1 : 0; - return this._fullScreen || right - left < 50 ? null : ( + return this._fullScreen || (xRight - xPos) * 2 < 50 ? null : (
-
+
{this.UIButtons}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index e94ef8534..8cf657da4 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -142,6 +142,7 @@ export namespace DashUploadUtils { const result = await UploadImage(path, basename(path)); return { source: file, result }; } + return { source: file, result: { name: 'Unsupported image format', message: `Could not upload unsupported file (${name}). Please convert to an .jpg` } }; case 'video': if (format.includes('x-matroska')) { console.log('case video'); @@ -179,6 +180,7 @@ export namespace DashUploadUtils { if (videoFormats.includes(format)) { return MoveParsedFile(file, Directory.videos); } + return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${name}). Please convert to an .mp4` } }; case 'application': if (applicationFormats.includes(format)) { return UploadPdf(file); @@ -191,6 +193,7 @@ export namespace DashUploadUtils { if (audioFormats.includes(format)) { return UploadAudio(file, format); } + return { source: file, result: { name: 'Unsupported audio format', message: `Could not upload unsupported file (${name}). Please convert to an .mp3` } }; case 'text': if (types[1] == 'csv') { return UploadCsv(file); -- cgit v1.2.3-70-g09d2