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/trails') 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/trails') 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 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/trails') 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 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/trails') 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/trails') 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 e48286293fb37e8af5c448c65c1926e813e8ee1f Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 3 Aug 2022 09:27:21 -0400 Subject: from last --- src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 6 +++--- src/client/views/nodes/trails/PresBox.tsx | 1 - src/client/views/nodes/trails/PresElementBox.tsx | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes/trails') diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index 697f8f47f..00bedafbe 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -42,9 +42,9 @@ export class MapBoxInfoWindow extends React.Component doc.type === DocumentType.RTF; - addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, "data", d), true as boolean); - removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, "data", d), true as boolean); + childFitWidth = (doc: Doc) => doc.type === DocumentType.RTF; + addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, 'data', d), true as boolean); + removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean); render() { return ( diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index dc01d8305..6e5eb3300 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -144,7 +144,6 @@ export class PresBox extends ViewBoxBaseComponent() { 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/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 5ac506f21..0cf15d297 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -202,7 +202,6 @@ export class PresElementBox extends ViewBoxBaseComponent() { /** * Function to drag and drop the pres element to a diferent location */ - //TODO: this is what you need to look into startDrag = (e: PointerEvent) => { const miniView: boolean = this.toolbarWidth <= 100; const activeItem = this.rootDoc; -- cgit v1.2.3-70-g09d2 From 513dcaa2d8c86f1cb5236ce89062cace6f418d1b Mon Sep 17 00:00:00 2001 From: bobzel 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/trails') 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 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/trails') 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 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/trails') 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 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/trails') 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 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/trails') 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/trails') 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 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/trails') 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/trails') 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/trails') 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 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/trails') 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 : (