From 820d40eea4eeb5977889e0ef6c35f9092df44b4b Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 10 Aug 2022 17:08:47 -0400 Subject: created placeholder loading box but have to get location of final doc to update with loading box --- src/client/documents/Documents.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c7ea04839..6ccb4358a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -48,6 +48,7 @@ import { KeyValueBox } from '../views/nodes/KeyValueBox'; import { LabelBox } from '../views/nodes/LabelBox'; import { LinkBox } from '../views/nodes/LinkBox'; import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup'; +import { LoadingBox } from '../views/nodes/LoadingBox'; import { MapBox } from '../views/nodes/MapBox/MapBox'; import { PDFBox } from '../views/nodes/PDFBox'; import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox'; @@ -648,6 +649,13 @@ export namespace Docs { options: { _fitWidth: true, nativeDimModifiable: true, links: '@links(self)' }, }, ], + [ + DocumentType.LOADING, + { + layout: { view: LoadingBox, dataField: defaultDataKey }, + options: { _fitWidth: true, _fitHeight: true, nativeDimModifiable: true, links: '@links(self)' }, + }, + ], ]); const suffix = 'Proto'; @@ -875,6 +883,16 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } + export function LoadingDocument(title: string, text: string, width?: number, height?: number, options: DocumentOptions = {}) { + let myWidth = 300; + let myHeight = 300; + if (height && width) { + myWidth = width; + myHeight = height; + } + return InstanceFromProto(Prototypes.get(DocumentType.LOADING), '', { ...options, title, text, _width: myWidth, _height: myHeight }); + } + export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } -- 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/documents/Documents.ts') 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 bb2b38b0e47eaf8e64554d101b605bf35a178239 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Tue, 16 Aug 2022 16:43:01 -0400 Subject: updated placeholder --- src/.DS_Store | Bin 10244 -> 10244 bytes src/client/documents/Documents.ts | 63 +++++++++++++------ src/client/util/CurrentUserUtils.ts | 1 + src/client/views/collections/CollectionSubView.tsx | 70 +++++++-------------- src/client/views/nodes/LoadingBox.tsx | 25 ++++++++ 5 files changed, 94 insertions(+), 65 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/.DS_Store b/src/.DS_Store index 4751acf44..4ed785983 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6ccb4358a..d8497e3af 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,4 +1,5 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { files } from 'jszip'; import { action, runInAction } from 'mobx'; import { basename } from 'path'; import { DateField } from '../../fields/DateField'; @@ -794,7 +795,7 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string) { + function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDoc?: Doc) { const viewKeys = ['x', 'y', 'system']; // keys that should be addded to the view document even though they don't begin with an "_" const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_'); @@ -813,13 +814,22 @@ export namespace Docs { // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. dataProps[fieldKey + '-annotations'] = new List(); dataProps[fieldKey + '-sidebar'] = new List(); - const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); + + const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); + + if (placeholderDoc) { + dataDoc.proto = proto; + } const viewFirstProps: { [id: string]: any } = {}; viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; viewFirstProps['acl-Override'] = 'None'; viewFirstProps.author = Doc.CurrentUserEmail; - const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); + let viewDoc: Doc; + if (placeholderDoc) { + viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); + } + viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); Doc.assign(viewDoc, viewProps, true, true); ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); @@ -883,14 +893,12 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - export function LoadingDocument(title: string, text: string, width?: number, height?: number, options: DocumentOptions = {}) { - let myWidth = 300; - let myHeight = 300; - if (height && width) { - myWidth = width; - myHeight = height; - } - return InstanceFromProto(Prototypes.get(DocumentType.LOADING), '', { ...options, title, text, _width: myWidth, _height: myHeight }); + export const filesToDocs = new Map(); + + export function LoadingDocument(file: File, options: DocumentOptions, ytString?: string) { + const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), file.name, { _height: 300, _width: 300, ...options }); + // filesToDocs.set(loading, file); + return loading; } export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { @@ -969,13 +977,13 @@ export namespace Docs { return I; } - export function PdfDocument(url: string, options: DocumentOptions = {}) { + export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { const width = options._width || undefined; const height = options._height || undefined; const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width); - return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(url), options); + return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(url), options, undefined, undefined, undefined, overwriteDoc); } export function WebDocument(url: string, options: DocumentOptions = {}) { @@ -1462,8 +1470,8 @@ export namespace DocUtils { return created; } - export async function DocumentFromType(type: string, path: string, options: DocumentOptions): Promise> { - let ctor: ((path: string, options: DocumentOptions) => Doc | Promise) | undefined = undefined; + export async function DocumentFromType(type: string, path: string, options: DocumentOptions, rootDoc?: Doc): Promise> { + let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise) | undefined = undefined; if (type.indexOf('image') !== -1) { ctor = Docs.Create.ImageDocument; if (!options._width) options._width = 300; @@ -1512,7 +1520,7 @@ export namespace DocUtils { options = { ...options, _width: 400, _height: 512, title: path }; } - return ctor ? ctor(path, options) : undefined; + return ctor ? ctor(path, options, rootDoc) : undefined; } export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void { @@ -1728,14 +1736,14 @@ export namespace DocUtils { return dd; } - async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions) { + async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, rootDoc?: Doc) { if (result instanceof Error) { alert(`Upload failed: ${result.message}`); return; } const full = { ...options, _width: 400, title: name }; const pathname = Utils.prepend(result.accessPaths.agnostic.client); - const doc = await DocUtils.DocumentFromType(type, pathname, full); + const doc = await DocUtils.DocumentFromType(type, pathname, full, rootDoc); if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; @@ -1813,6 +1821,25 @@ export namespace DocUtils { return generatedDocuments; } + export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) { + const generatedDocuments: Doc[] = []; + Networking.UploadFilesToServer([file]).then(upfiles => { + const { + source: { name, type }, + result, + } = upfiles.lastElement(); + console.log(name, type); + name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + }); + } + + export function generatePlaceHolder(file: File, options: DocumentOptions) { + return Docs.Create.LoadingDocument(file, options); + // placeholder.file = file + // TODO: nda - modify loading doc so it only takes in options + // Docs.Create.LoadingDocument(options, ) + } + // copies the specified drag factory document export function copyDragFactory(dragFactory: Doc) { if (!dragFactory) return undefined; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d19874720..492513a61 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -196,6 +196,7 @@ export class CurrentUserUtils { makeIconTemplate(DocumentType.RTF, "text", { iconTemplate:DocumentType.LABEL, _showTitle: "creationDate"}), makeIconTemplate(DocumentType.IMG, "data", { iconTemplate:DocumentType.IMG, _height: undefined}), makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), + makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}), makeIconTemplate(DocumentType.BUTTON,"data", { iconTemplate:DocumentType.FONTICON}), //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 79f629072..1ff4f5ab8 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -449,48 +449,6 @@ export function CollectionSubView(moreProps?: X) { this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); } - /** - * Creates a placeholder doc view for files being uploaded and removes placeholder docs once files are uplodaded. - * - * @param files the files to upload that we want to create placeholders for - * @param options the document options (primarily the x and y coordinates to put doc) - * @param text in the case of youtube the text is the url to the video - * @returns a disposer action that removes the placeholders created after files get uploaded - */ - placeHolderDisposer = (files: File[] | string, options: DocumentOptions, text: string) => { - // TODO: nda - create a specialized view for placeholder upload with a spinner and ability to retry upload - let placeholders: Doc[] = []; - // handle yt case - if (typeof files === 'string') { - placeholders.push(Docs.Create.LoadingDocument('Loading...', text, 500, 500, { ...options })); - // placeholders.push(Docs.Create.TextDocument('Loading: ' + text, { ...options, title: text, _width: 400, _height: 30 })); - } else { - // every other doc type is an array of File - - // Get the file names as a text - let textStr = ''; - files.forEach(file => { - textStr += file.name + '\n'; - }); - placeholders.push(Docs.Create.LoadingDocument('Loading...', textStr, 500, 500, { ...options })); - - // placeholders.push(Docs.Create.TextDocument('Loading: \n' + textStr, { ...options, title: files.length + ' files', _width: 500, _height: files.length * 40 })); - } - // disposer action to remove placeholders once files are uploaded - const remove = action(() => { - if (!this.props.DataDoc) { - return; - } - for (let i = 0; i < placeholders.length; i++) { - Doc.RemoveDocFromList(this.props.DataDoc, 'data', placeholders[i]); - } - }); - placeholders.forEach(pl => { - this.addDocument(pl); - }); - return remove; - }; - slowLoadDocuments = async ( files: File[] | string, options: DocumentOptions, @@ -501,13 +459,31 @@ export function CollectionSubView(moreProps?: X) { clientY: number, addDocument: (doc: Doc | Doc[]) => boolean ) => { + // create placeholder docs + // inside placeholder docs have some func that + // TODO: once loading thing is moved it should update the x and y of the file it is placeholder for - const disposer = this.placeHolderDisposer(files, options, text); + let pileUpDoc = undefined; // const disposer = OverlayView.Instance.addElement(, { x: clientX - 125, y: clientY - 125 }); if (typeof files === 'string') { - generatedDocuments.push(...(await DocUtils.uploadYoutubeVideo(files, options))); + // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder + // generatedDocuments.push(Docs.Create.LoadingDocument(files, options)); } else { - generatedDocuments.push(...(await DocUtils.uploadFilesToDocs(files, options))); + // uploadFilesToDocs and similar should return a placeholder, one for each placeholder + generatedDocuments.push( + ...files.map(file => { + const loading = Docs.Create.LoadingDocument(file, options); + // now that there is a doc do whatever slowload was going to do with that file + if (typeof file === 'string') { + // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder + // (await DocUtils.uploadYoutubeVideo(files, options))); + } else { + // uploadFilesToDocs and similar should return a placeholder, one for each placeholder + DocUtils.uploadFileToDoc(file, {}, loading); + } + return loading; + }) + ); } if (generatedDocuments.length) { // Creating a dash document @@ -523,7 +499,8 @@ export function CollectionSubView(moreProps?: X) { if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { - addDocument(DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!); + pileUpDoc = DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!; + addDocument(pileUpDoc); } else { generatedDocuments.forEach(addDocument); } @@ -535,7 +512,6 @@ export function CollectionSubView(moreProps?: X) { alert('Document upload failed - possibly an unsupported file type.'); } } - disposer(); }; } diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 0e0619241..9d4668dde 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -4,6 +4,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import * as React from 'react'; import './LoadingBox.scss'; import ReactLoading from 'react-loading'; +import { Docs, DocUtils } from '../../documents/Documents'; export interface LoadingBoxProps { title: string; @@ -16,6 +17,30 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { return FieldView.LayoutString(LoadingBox, fieldKey); } + componentDidMount() { + // const file = Docs.Create.filesToDocs.get(this.rootDoc); + // if (file) { + // console.log('Got to file'); + // Docs.Create.filesToDocs.delete(this.rootDoc); + // // now that there is a doc do whatever slowload was going to do with that file + // if (typeof file === 'string') { + // // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder + // // (await DocUtils.uploadYoutubeVideo(files, options))); + // } else { + // // uploadFilesToDocs and similar should return a placeholder, one for each placeholder + // DocUtils.uploadFileToDoc(file, {}, this.rootDoc); + // } + // } else { + // // check if file now exists on server or not + // // if it does we need to retreieve it and create the appropriate doc (rest of what uploadFileToDoc was doing minus uploading) + // // if it doesn't display an error message "upload failed" + // } + // query endpoints to: + // check if file now exists on server or not + // if it does we need to retreieve it and create the appropriate doc (rest of what uploadFileToDoc was doing minus uploading) + // if it doesn't display an error message "upload failed" + } + constructor(props: any) { super(props); } -- cgit v1.2.3-70-g09d2 From 27945b9a931fa9504404174dd08964556dc3aea2 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Tue, 16 Aug 2022 21:16:03 -0400 Subject: added loading for diff doc types --- src/client/documents/Documents.ts | 47 ++++++++++++++++------ src/client/views/collections/CollectionSubView.tsx | 11 ++--- src/client/views/nodes/LoadingBox.scss | 5 +++ src/client/views/nodes/LoadingBox.tsx | 12 +++--- 4 files changed, 48 insertions(+), 27 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d8497e3af..e54fe16de 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -844,9 +844,9 @@ export namespace Docs { return viewDoc; } - export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}) { + export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) { const imgField = url instanceof ImageField ? url : new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc); } export function PresDocument(options: DocumentOptions = {}) { @@ -857,12 +857,12 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); } - export function VideoDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options); + export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { + return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc); } - export function YoutubeDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options); + export function YoutubeDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { + return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options, undefined, undefined, undefined, overwriteDoc); } export function WebCamDocument(url: string, options: DocumentOptions = {}) { @@ -877,8 +877,16 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), '', options); } - export function AudioDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }); + export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { + return InstanceFromProto( + Prototypes.get(DocumentType.AUDIO), + new AudioField(url), + { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }, + undefined, + undefined, + undefined, + overwriteDoc + ); } export function RecordingDocument(url: string, options: DocumentOptions = {}) { @@ -895,8 +903,10 @@ export namespace Docs { export const filesToDocs = new Map(); - export function LoadingDocument(file: File, options: DocumentOptions, ytString?: string) { - const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), file.name, { _height: 300, _width: 300, ...options }); + export function LoadingDocument(file: File | string, options: DocumentOptions, ytString?: string) { + const fileName = typeof file == 'string' ? file : file.name; + options.title = fileName; + const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), fileName, { _height: 150, _width: 150, ...options }); // filesToDocs.set(loading, file); return loading; } @@ -1114,8 +1124,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } - export function DataVizDocument(url: string, options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }); + export function DataVizDocument(url: string, options?: DocumentOptions, overwriteDoc?: Doc) { + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }, undefined, undefined, undefined, overwriteDoc); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { @@ -1809,6 +1819,18 @@ export namespace DocUtils { } return generatedDocuments; } + + export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) { + const generatedDocuments: Doc[] = []; + Networking.UploadYoutubeToServer(videoId).then(upfiles => { + const { + source: { name, type }, + result, + } = upfiles.lastElement(); + name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + }); + } + export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { const generatedDocuments: Doc[] = []; const upfiles = await Networking.UploadFilesToServer(files); @@ -1828,7 +1850,6 @@ export namespace DocUtils { source: { name, type }, result, } = upfiles.lastElement(); - console.log(name, type); name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); }); } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 1ff4f5ab8..30467efa0 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -468,19 +468,16 @@ export function CollectionSubView(moreProps?: X) { if (typeof files === 'string') { // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder // generatedDocuments.push(Docs.Create.LoadingDocument(files, options)); + const loading = Docs.Create.LoadingDocument(files, options); + generatedDocuments.push(loading); + DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { // uploadFilesToDocs and similar should return a placeholder, one for each placeholder generatedDocuments.push( ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); // now that there is a doc do whatever slowload was going to do with that file - if (typeof file === 'string') { - // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder - // (await DocUtils.uploadYoutubeVideo(files, options))); - } else { - // uploadFilesToDocs and similar should return a placeholder, one for each placeholder - DocUtils.uploadFileToDoc(file, {}, loading); - } + DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) ); diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss index 239faa78e..e8890cd82 100644 --- a/src/client/views/nodes/LoadingBox.scss +++ b/src/client/views/nodes/LoadingBox.scss @@ -5,3 +5,8 @@ justify-content: center; background-color: #fdfdfd; } + +.text { + text-overflow: ellipsis; + text-align: center; +} diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 9d4668dde..96620aff9 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -4,12 +4,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import * as React from 'react'; import './LoadingBox.scss'; import ReactLoading from 'react-loading'; -import { Docs, DocUtils } from '../../documents/Documents'; - -export interface LoadingBoxProps { - title: string; - text: string; -} +import { StrCast } from '../../../fields/Types'; @observer export class LoadingBox extends ViewBoxAnnotatableComponent() { @@ -18,6 +13,7 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { } componentDidMount() { + console.log(this.rootDoc); // const file = Docs.Create.filesToDocs.get(this.rootDoc); // if (file) { // console.log('Got to file'); @@ -48,7 +44,9 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { render() { return (
- Loading: {this.dataDoc.text} +

Loading:

+

+

{StrCast(this.rootDoc.title)}

); -- cgit v1.2.3-70-g09d2 From 0178de4ab9ffd11630b700f9c02468b74beabd14 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 17 Aug 2022 12:31:26 -0400 Subject: fixed dragging docs on web and pdf to work better by temporarily adding transparent docs to the opaque layer so they can get drop events. cleaned eraser and pen interaction code and made erasing strokes work faster and avoid hanging by not intersecting strokes that are already partially deleted. --- src/Utils.ts | 3 +- src/client/documents/Documents.ts | 26 +--- src/client/views/collections/CollectionSubView.tsx | 12 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 140 ++++++--------------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/VideoBox.tsx | 11 +- src/client/views/nodes/WebBox.scss | 17 +-- src/client/views/nodes/WebBox.tsx | 110 ++++++++-------- src/client/views/pdf/PDFViewer.tsx | 75 +++++------ 10 files changed, 153 insertions(+), 252 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/Utils.ts b/src/Utils.ts index 528a429d0..9e002ebd4 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -111,6 +111,7 @@ export namespace Utils { const isTransparentFunctionHack = 'isTransparent(__value__)'; export const noRecursionHack = '__noRecursion'; + export const noDragsDocFilter = 'noDragDocs:any:check'; export function IsRecursiveFilter(val: string) { return !val.includes(noRecursionHack); } @@ -125,7 +126,7 @@ export namespace Utils { // bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one return `backgroundColor:${isTransparentFunctionHack},${noRecursionHack}:x`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field } - export function PropUnsetFilter(prop: string) { + export function IsPropUnsetFilter(prop: string) { return `${prop}:any,${noRecursionHack}:unset`; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cf9ed43e1..e579bfd8a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1143,30 +1143,6 @@ export namespace Docs { } export namespace DocUtils { - export function Excluded(d: Doc, docFilters: string[]) { - const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields - docFilters.forEach(filter => { - const fields = filter.split(':'); - const key = fields[0]; - const value = fields[1]; - const modifiers = fields[2]; - if (!filterFacets[key]) { - filterFacets[key] = {}; - } - filterFacets[key][value] = modifiers; - }); - - if (d.z) return false; - for (const facetKey of Object.keys(filterFacets)) { - const facet = filterFacets[facetKey]; - const xs = Object.keys(facet).filter(value => facet[value] === 'x'); - const failsNotEqualFacets = xs?.some(value => Doc.matchFieldValue(d, facetKey, value)); - if (failsNotEqualFacets) { - return true; - } - } - return false; - } /** * @param docs * @param docFilters @@ -1200,7 +1176,7 @@ export namespace DocUtils { return false; } - for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies')) { + for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(':')[0])) { const facet = filterFacets[facetKey]; // facets that match some value in the field of the document (e.g. some text field) diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5479929bd..e33bb77de 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -88,11 +88,11 @@ export function CollectionSubView(moreProps?: X) { } collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._docFilters); collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec('string'), []); + // child filters apply to the descendants of the documents in this collection childDocFilters = () => [...(this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; + // unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack unrecursiveDocFilters = () => [...(this.props.docFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])]; childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()]; - IsFiltered = () => - this.collectionFilters().length || this.collectionRangeDocFilters().length ? 'hasFilter' : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? 'inheritsFilter' : undefined; searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs); @computed.struct get childDocs() { TraceMobx(); @@ -122,13 +122,11 @@ export function CollectionSubView(moreProps?: X) { return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one } - // console.log(Doc.ActiveDashboard._docFilters); - // if (!this.props.Document._docFilters && this.props.Document.currentFilter) { - // (this.props.Document.currentFilter as Doc).filterBoolean = (this.props.ContainingCollectionDoc?.currentFilter as Doc)?.filterBoolean; - // } const docsforFilter: Doc[] = []; childDocs.forEach(d => { - // if (DocUtils.Excluded(d, docFilters)) return; + // dragging facets + const dragged = this.props.docFilters?.().some(f => f.includes(Utils.noDragsDocFilter)); + if (dragged && DragManager.docsBeingDragged.includes(d)) return false; let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0; if (notFiltered) { notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f3074543b..45a5e30ff 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -109,8 +109,6 @@ export class CollectionFreeFormView extends CollectionSubView, props: Opt, property: string) => { let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 - if (property !== StyleProp.BackgroundColor) return styleProp; - const cluster = NumCast(doc?.cluster); - if (this.Document._useClusters) { - if (this._clusterSets.length <= cluster) { - setTimeout(() => doc && this.updateCluster(doc)); - } else { - // choose a cluster color from a palette - const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; - styleProp = colors[cluster % colors.length]; - const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); - // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.map(s => (styleProp = StrCast(s.backgroundColor))); - } - } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; + switch (property) { + case StyleProp.BackgroundColor: + const cluster = NumCast(doc?.cluster); + if (this.Document._useClusters) { + if (this._clusterSets.length <= cluster) { + setTimeout(() => doc && this.updateCluster(doc)); + } else { + // choose a cluster color from a palette + const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; + styleProp = colors[cluster % colors.length]; + const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); + // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document + set?.map(s => (styleProp = StrCast(s.backgroundColor))); + } + } + break; + } return styleProp; }; @@ -523,17 +524,13 @@ export class CollectionFreeFormView extends CollectionSubView { - if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener('pointermove', this.onEraserMove); - document.removeEventListener('pointerup', this.onEraserUp); - this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc)); - this._deleteList = []; - this._batch?.end(); - } - }; - - @action - onPointerUp = (e: PointerEvent): void => { - if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); - this.removeMoveListeners(); - this.removeEndListeners(); - } + this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc)); + this._deleteList = []; + this._batch?.end(); }; onClick = (e: React.MouseEvent) => { @@ -752,46 +735,42 @@ export class CollectionFreeFormView extends CollectionSubView { + onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => { const currPoint = { X: e.clientX, Y: e.clientY }; - this.getEraserIntersections({ X: this._lastX, Y: this._lastY }, currPoint).forEach(intersect => { + this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => { if (!this._deleteList.includes(intersect.inkView)) { this._deleteList.push(intersect.inkView); SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1'); SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black'); // create a new curve by appending all curves of the current segment together in order to render a single new stroke. - !e.shiftKey && + if (!e.shiftKey) { this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment => GestureOverlay.Instance.dispatchGesture( GestureUtils.Gestures.Stroke, segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) ) ); + } // Lower ink opacity to give the user a visual indicator of deletion. intersect.inkView.layoutDoc.opacity = 0.5; + intersect.inkView.layoutDoc.dontIntersect = true; } }); - this._lastX = currPoint.X; - this._lastY = currPoint.Y; - - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); + return false; }; @action - onPointerMove = (e: PointerEvent): void => { - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; + onPointerMove = (e: PointerEvent): boolean => { + if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { Doc.ActiveTool = InkTool.None; if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { if (this.tryDragCluster(e, this._hitCluster)) { - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); + return true; } else this.pan(e); - e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers - e.preventDefault(); } + return false; }; /** @@ -801,6 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView { const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) }; const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) }; + return this.childDocs .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) @@ -888,7 +868,7 @@ export class CollectionFreeFormView extends CollectionSubView doc.type === DocumentType.INK) + .filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect) .forEach(doc => { const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke; const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; @@ -919,7 +899,6 @@ export class CollectionFreeFormView extends CollectionSubView only want to do this when collection is selected' @@ -967,13 +946,6 @@ export class CollectionFreeFormView extends CollectionSubView { - switch (this._pullDirection) { - case 'left': - case 'right': - case 'top': - case 'bottom': - CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: 'New Collection' }), this._pullDirection); - } - - this._pullDirection = ''; - this._pullCoords = [0, 0]; - - document.removeEventListener('pointermove', this.onPointerMove); - document.removeEventListener('pointerup', this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); }; @@ -1380,7 +1321,7 @@ export class CollectionFreeFormView extends CollectionSubView -
+
{this.layoutDoc._backgroundGridShow ? (
} -
{ // uncomment to show snap lines
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 78f351f4f..e19e2d525 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable, trace } from 'mobx'; +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; @@ -6,13 +6,12 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DashColor, numberRange, OmitKeys } from '../../../Utils'; +import { numberRange } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { DocComponent } from '../DocComponent'; -import { InkingStroke } from '../InkingStroke'; import { StyleProp } from '../StyleProvider'; import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps } from './DocumentView'; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bcc55eab4..172adcafe 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -970,7 +970,11 @@ export class DocumentViewInternal extends DocComponent StrListCast(this.props.Document._docFilters); collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters); @computed get showFilterIcon() { - return this.collectionFilters().length || this.collectionRangeDocFilters().length ? 'hasFilter' : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? 'inheritsFilter' : undefined; + return this.collectionFilters().length || this.collectionRangeDocFilters().length + ? 'hasFilter' + : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragsDocFilter).length || this.props.docRangeFilters().length + ? 'inheritsFilter' + : undefined; } rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; panelHeight = () => this.props.PanelHeight() - this.headerMargin; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index a3d501153..0ff15f93b 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -831,16 +831,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent 1) { - return { height: '100%' }; - } else { - return { width: '100%' }; + //prettier-ignore + return ({ height: '100%' }); } + //prettier-ignore + return ({ width: '100%' }); } // for zoom slider, sets timeline waveform zoom - zoom = (zoom: number) => { - this.timeline?.setZoom(zoom); - }; + zoom = (zoom: number) => this.timeline?.setZoom(zoom); // plays link playLink = (doc: Doc) => { diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index d8dd074a5..85986ff27 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -1,9 +1,11 @@ -@import "../global/globalCssVariables.scss"; - +@import '../global/globalCssVariables.scss'; .webBox { height: 100%; - position: relative; + width: 100%; + top: 0; + left: 0; + position: absolute; display: flex; .webBox-sideResizer { @@ -84,7 +86,6 @@ background: none; } - .webBox-overlayCont { position: absolute; width: calc(100% - 40px); @@ -95,7 +96,7 @@ justify-content: center; align-items: center; overflow: hidden; - transition: left .5s; + transition: left 0.5s; pointer-events: all; .webBox-searchBar { @@ -158,7 +159,7 @@ left: 0; cursor: text; padding: 15px; - height: 100% + height: 100%; } .webBox-cont { @@ -235,7 +236,7 @@ height: 25px; align-items: center; - >svg { + > svg { margin: auto; } } @@ -257,4 +258,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ca9f363c1..6c2e42f86 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as WebRequest from 'web-request'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; @@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; @@ -35,6 +35,7 @@ import { LinkDocPreview } from './LinkDocPreview'; import { VideoBox } from './VideoBox'; import './WebBox.scss'; import React = require('react'); +import { DragManager } from '../../util/DragManager'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -818,6 +819,56 @@ export class WebBox extends ViewBoxAnnotatableComponent string[]) => ( + + ); + return ( +
this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} + onPointerDown={this.onMarqueeDown}> +
+ {this.content} + {
{renderAnnotations(this.transparentFilter)}
} + {renderAnnotations(this.opaqueFilter)} + {this.annotationLayer} +
+
+ ); + } + @computed get searchUI() { return (
e.stopPropagation()} style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}> @@ -859,9 +910,8 @@ export class WebBox extends ViewBoxAnnotatableComponent this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; - basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')]; transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; childStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (doc.textInlineAnnotations) return 'none'; @@ -871,37 +921,11 @@ export class WebBox extends ViewBoxAnnotatableComponent (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'); annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() ? 'all' : 'none'); render() { - const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; + const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); const scale = previewScale * (this.props.NativeDimScaling?.() || 1); - const renderAnnotations = (docFilters?: () => string[]) => ( - - ); return ( -
+
-
{ - e.stopPropagation(); - e.preventDefault(); - }} // block wheel events from propagating since they're handled by the iframe - onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} - onPointerDown={this.onMarqueeDown}> -
- {this.content} -
{renderAnnotations(this.transparentFilter)}
- {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? null : renderAnnotations()} - {this.annotationLayer} -
-
+ {this.webpage} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
{ overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1)); panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); - basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter('textInlineAnnotations')]; transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; childStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (doc.textInlineAnnotations) return 'none'; @@ -498,56 +498,43 @@ export class PDFViewer extends React.Component { return this.props.styleProvider?.(doc, props, property); }; - renderAnnotations = (docFilters?: () => string[], dontRender?: boolean) => ( - + renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => ( +
+ +
); @computed get overlayTransparentAnnotations() { - return this.renderAnnotations(this.transparentFilter, false); + return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length ? 'none' : undefined); } @computed get overlayOpaqueAnnotations() { - return this.renderAnnotations(this.opaqueFilter, false); - } - @computed get overlayClickableAnnotations() { - return
{this.renderAnnotations(undefined, true)}
; + return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined); } @computed get overlayLayer() { return (
-
- {this.overlayTransparentAnnotations} -
-
anno.mixBlendMode) ? 'hard-light' : undefined, - transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})`, - }}> - {this.overlayOpaqueAnnotations} - {this.overlayClickableAnnotations} -
+ {this.overlayTransparentAnnotations} + {this.overlayOpaqueAnnotations}
); } -- cgit v1.2.3-70-g09d2 From 94c38310c6b54d93e907007f20ba032d12697ca0 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Thu, 18 Aug 2022 12:33:34 -0400 Subject: fixed sizing bug --- src/client/documents/Documents.ts | 14 +++++-------- src/client/views/collections/CollectionSubView.tsx | 2 ++ src/client/views/nodes/LoadingBox.tsx | 24 ++++++++++++++++++---- 3 files changed, 27 insertions(+), 13 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e54fe16de..f3ab7c788 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -827,9 +827,12 @@ export namespace Docs { viewFirstProps.author = Doc.CurrentUserEmail; let viewDoc: Doc; if (placeholderDoc) { + placeholderDoc._height = Number(options._height); + placeholderDoc._width = Number(options._width); viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); + } else { + viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); } - viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); Doc.assign(viewDoc, viewProps, true, true); ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); @@ -901,7 +904,7 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - export const filesToDocs = new Map(); + export const filesToDocs = new Map(); export function LoadingDocument(file: File | string, options: DocumentOptions, ytString?: string) { const fileName = typeof file == 'string' ? file : file.name; @@ -1854,13 +1857,6 @@ export namespace DocUtils { }); } - export function generatePlaceHolder(file: File, options: DocumentOptions) { - return Docs.Create.LoadingDocument(file, options); - // placeholder.file = file - // TODO: nda - modify loading doc so it only takes in options - // Docs.Create.LoadingDocument(options, ) - } - // copies the specified drag factory document export function copyDragFactory(dragFactory: Doc) { if (!dragFactory) return undefined; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 30467efa0..fd2c722d2 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -470,6 +470,7 @@ export function CollectionSubView(moreProps?: X) { // generatedDocuments.push(Docs.Create.LoadingDocument(files, options)); const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); + Docs.Create.filesToDocs.set(loading, files); DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { // uploadFilesToDocs and similar should return a placeholder, one for each placeholder @@ -477,6 +478,7 @@ export function CollectionSubView(moreProps?: X) { ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); // now that there is a doc do whatever slowload was going to do with that file + Docs.Create.filesToDocs.set(loading, file); DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 96620aff9..249461b67 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -5,6 +5,8 @@ import * as React from 'react'; import './LoadingBox.scss'; import ReactLoading from 'react-loading'; import { StrCast } from '../../../fields/Types'; +import { computed, observable } from 'mobx'; +import { Docs } from '../../documents/Documents'; @observer export class LoadingBox extends ViewBoxAnnotatableComponent() { @@ -12,6 +14,12 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { return FieldView.LayoutString(LoadingBox, fieldKey); } + @computed + private get isLoading() { + const file = Docs.Create.filesToDocs.get(this.rootDoc); + return file ? true : false; + } + componentDidMount() { console.log(this.rootDoc); // const file = Docs.Create.filesToDocs.get(this.rootDoc); @@ -44,10 +52,18 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { render() { return (
-

Loading:

-

-

{StrCast(this.rootDoc.title)}

- + {this.isLoading ? ( +
+

Loading:

+

+ {StrCast(this.rootDoc.title)} + +
+ ) : ( +
+ Error Loading File: {StrCast(this.rootDoc.title)} +
+ )}
); } -- cgit v1.2.3-70-g09d2 From b7c4d65a3bf04ff7d2c10d93be282a1b0a4650b3 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Fri, 19 Aug 2022 12:32:23 -0400 Subject: added comments, cleaned up code, could not get spinner to center --- src/client/documents/Documents.ts | 8 ++- src/client/views/collections/CollectionSubView.tsx | 4 +- src/client/views/nodes/LoadingBox.scss | 21 +++++++ src/client/views/nodes/LoadingBox.tsx | 64 ++++++++++------------ 4 files changed, 58 insertions(+), 39 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f3ab7c788..9e140ccd1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -815,6 +815,7 @@ export namespace Docs { dataProps[fieldKey + '-annotations'] = new List(); dataProps[fieldKey + '-sidebar'] = new List(); + // users placeholderDoc as proto if it exists const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); if (placeholderDoc) { @@ -826,6 +827,7 @@ export namespace Docs { viewFirstProps['acl-Override'] = 'None'; viewFirstProps.author = Doc.CurrentUserEmail; let viewDoc: Doc; + // determines whether viewDoc should be created using placeholder Doc or deafult if (placeholderDoc) { placeholderDoc._height = Number(options._height); placeholderDoc._width = Number(options._width); @@ -904,13 +906,13 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - export const filesToDocs = new Map(); + // Mapping of all loading docs to files, i.e. keeps track of files being uploaded in current session + export const docsToFiles = new Map(); export function LoadingDocument(file: File | string, options: DocumentOptions, ytString?: string) { const fileName = typeof file == 'string' ? file : file.name; options.title = fileName; - const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), fileName, { _height: 150, _width: 150, ...options }); - // filesToDocs.set(loading, file); + const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), fileName, { _height: 150, _width: 200, ...options }); return loading; } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index fd2c722d2..eef485b95 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -470,7 +470,7 @@ export function CollectionSubView(moreProps?: X) { // generatedDocuments.push(Docs.Create.LoadingDocument(files, options)); const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); - Docs.Create.filesToDocs.set(loading, files); + Docs.Create.docsToFiles.set(loading, files); DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { // uploadFilesToDocs and similar should return a placeholder, one for each placeholder @@ -478,7 +478,7 @@ export function CollectionSubView(moreProps?: X) { ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); // now that there is a doc do whatever slowload was going to do with that file - Docs.Create.filesToDocs.set(loading, file); + Docs.Create.docsToFiles.set(loading, file); DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss index e8890cd82..f6912f547 100644 --- a/src/client/views/nodes/LoadingBox.scss +++ b/src/client/views/nodes/LoadingBox.scss @@ -6,7 +6,28 @@ background-color: #fdfdfd; } +.textContainer { + margin: 5px; +} + +.textContainer { + justify-content: center; + align-content: center; +} + +.textContainer, .text { + overflow: hidden; text-overflow: ellipsis; + max-width: 80%; + text-align: center; +} + +.headerText { + text-align: center; + font-weight: bold; +} + +.spinner { text-align: center; } diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 249461b67..ab8878fc1 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -5,46 +5,42 @@ import * as React from 'react'; import './LoadingBox.scss'; import ReactLoading from 'react-loading'; import { StrCast } from '../../../fields/Types'; -import { computed, observable } from 'mobx'; +import { computed } from 'mobx'; import { Docs } from '../../documents/Documents'; +/** + * LoadingBox Class represents a placeholder doc for documents that are currently + * being uploaded to the server and being fetched by the client. The LoadingBox doc is then used to + * generate the actual type of doc that is required once the document has been successfully uploaded. + * + * Design considerations: + * We are using the docToFiles map in Documents to keep track of all files being uploaded in one session of the client. + * If the file is not found we assume an error has occurred with the file upload, e.g. it has been interrupted by a client refresh + * or network issues. The docToFiles essentially gets reset everytime the page is refreshed. + * + * TODOs: + * 1) ability to query server to retrieve files that already exist if users upload duplicate files. + * 2) ability to restart upload if there is an error + * 3) detect network error and notify the user + * 4 )if file upload gets interrupted, it still gets uploaded to the server if there are no network interruptions which leads to unused space. this could be + * handled with (1) + * + * @author naafiyan + */ @observer export class LoadingBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LoadingBox, fieldKey); } - @computed - private get isLoading() { - const file = Docs.Create.filesToDocs.get(this.rootDoc); + /** + * Returns true if file is uploading, false otherwise + */ + @computed private get isLoading(): boolean { + const file = Docs.Create.docsToFiles.get(this.rootDoc); return file ? true : false; } - componentDidMount() { - console.log(this.rootDoc); - // const file = Docs.Create.filesToDocs.get(this.rootDoc); - // if (file) { - // console.log('Got to file'); - // Docs.Create.filesToDocs.delete(this.rootDoc); - // // now that there is a doc do whatever slowload was going to do with that file - // if (typeof file === 'string') { - // // uploadYoutubeVideo and similar should return a placeholder, one for each placeholder - // // (await DocUtils.uploadYoutubeVideo(files, options))); - // } else { - // // uploadFilesToDocs and similar should return a placeholder, one for each placeholder - // DocUtils.uploadFileToDoc(file, {}, this.rootDoc); - // } - // } else { - // // check if file now exists on server or not - // // if it does we need to retreieve it and create the appropriate doc (rest of what uploadFileToDoc was doing minus uploading) - // // if it doesn't display an error message "upload failed" - // } - // query endpoints to: - // check if file now exists on server or not - // if it does we need to retreieve it and create the appropriate doc (rest of what uploadFileToDoc was doing minus uploading) - // if it doesn't display an error message "upload failed" - } - constructor(props: any) { super(props); } @@ -53,15 +49,15 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { return (
{this.isLoading ? ( -
-

Loading:

-

+
+

Loading:

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

Error Loading File:

+ {StrCast(this.rootDoc.title)}
)}
-- cgit v1.2.3-70-g09d2 From a26ee13f1714fa6f6234310558a25ae8b493fa7d Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Fri, 19 Aug 2022 12:37:55 -0400 Subject: fixed comment typo --- src/client/documents/Documents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8d86e35c5..ed3bf6d32 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -827,7 +827,7 @@ export namespace Docs { viewFirstProps['acl-Override'] = 'None'; viewFirstProps.author = Doc.CurrentUserEmail; let viewDoc: Doc; - // determines whether viewDoc should be created using placeholder Doc or deafult + // determines whether viewDoc should be created using placeholder Doc or default if (placeholderDoc) { placeholderDoc._height = Number(options._height); placeholderDoc._width = Number(options._width); -- cgit v1.2.3-70-g09d2 From da1ea6120ebdd1986eb55a749a95f0397ccc2373 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Fri, 19 Aug 2022 12:40:05 -0400 Subject: fixed comment typo --- src/client/documents/Documents.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ed3bf6d32..5f416edc0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1474,7 +1474,15 @@ export namespace DocUtils { return created; } - export async function DocumentFromType(type: string, path: string, options: DocumentOptions, rootDoc?: Doc): Promise> { + /** + * + * @param type the type of file. + * @param path the path to the file. + * @param options the document options. + * @param overwriteDoc the placeholder loading doc. + * @returns + */ + export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise> { let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise) | undefined = undefined; if (type.indexOf('image') !== -1) { ctor = Docs.Create.ImageDocument; @@ -1524,7 +1532,7 @@ export namespace DocUtils { options = { ...options, _width: 400, _height: 512, title: path }; } - return ctor ? ctor(path, options, rootDoc) : undefined; + return ctor ? ctor(path, options, overwriteDoc) : undefined; } export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void { -- 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/documents/Documents.ts') 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 02fe10d4a15cb5f72f77da88dc7e1fa4b39346c5 Mon Sep 17 00:00:00 2001 From: mehekj Date: Wed, 24 Aug 2022 20:07:42 -0400 Subject: added schema row doctype --- src/client/documents/DocumentTypes.ts | 3 ++- src/client/documents/Documents.ts | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 9dfadf778..c9b42f2b9 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -39,6 +39,7 @@ export enum DocumentType { SEARCHITEM = 'searchitem', COMPARISON = 'comparison', GROUP = 'group', + SCHEMAROW = 'schemarow', LINKDB = 'linkdb', // database of links ??? why do we have this SCRIPTDB = 'scriptdb', // database of scripts @@ -63,5 +64,5 @@ export enum CollectionViewType { Grid = 'grid', Pile = 'pileup', StackedTimeline = 'stacked timeline', - NoteTaking = "notetaking" + NoteTaking = 'notetaking', } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e579bfd8a..99c04b673 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -27,6 +27,7 @@ import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; +import { SchemaRowBox } from '../views/collections/collectionSchema/SchemaRowBox'; import { CollectionView } from '../views/collections/CollectionView'; import { ContextMenu } from '../views/ContextMenu'; import { ContextMenuProps } from '../views/ContextMenuItem'; @@ -648,6 +649,12 @@ export namespace Docs { options: { _fitWidth: true, nativeDimModifiable: true, links: '@links(self)' }, }, ], + [ + DocumentType.SCHEMAROW, + { + layout: { view: SchemaRowBox, dataField: defaultDataKey }, + }, + ], ]); const suffix = 'Proto'; @@ -1101,6 +1108,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } + export function SchemaRowDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.SCHEMAROW), undefined, { ...(options || {}) }); + } + export function DataVizDocument(url: string, options?: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }); } -- cgit v1.2.3-70-g09d2 From 7f2556568ab635274c483d102fa4555d12e14835 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 30 Aug 2022 13:55:41 -0400 Subject: fixed loadingDocument issues with overwriting height with NaN and setting 'data' field unnecessarily and at the expense of being able to set it for replacement docs when using animation frames --- src/client/documents/Documents.ts | 34 ++++++++++------------ src/client/views/GlobalKeyHandler.ts | 9 +++--- src/client/views/collections/CollectionSubView.tsx | 6 ++-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 6 ++-- src/client/views/nodes/LoadingBox.tsx | 29 ++++-------------- 5 files changed, 30 insertions(+), 54 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9446eb70c..b22e16633 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -653,7 +653,7 @@ export namespace Docs { [ DocumentType.LOADING, { - layout: { view: LoadingBox, dataField: defaultDataKey }, + layout: { view: LoadingBox, dataField: '' }, options: { _fitWidth: true, _fitHeight: true, nativeDimModifiable: true, links: '@links(self)' }, }, ], @@ -806,14 +806,15 @@ export namespace Docs { dataProps.isPrototype = true; dataProps.author = Doc.CurrentUserEmail; dataProps.creationDate = new DateField(); - dataProps[`${fieldKey}-lastModified`] = new DateField(); - - dataProps[fieldKey] = data; - - // so that the list of annotations is already initialised, prevents issues in addonly. - // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. - dataProps[fieldKey + '-annotations'] = new List(); - dataProps[fieldKey + '-sidebar'] = new List(); + if (fieldKey) { + dataProps[`${fieldKey}-lastModified`] = new DateField(); + dataProps[fieldKey] = data; + + // so that the list of annotations is already initialised, prevents issues in addonly. + // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. + dataProps[fieldKey + '-annotations'] = new List(); + dataProps[fieldKey + '-sidebar'] = new List(); + } // users placeholderDoc as proto if it exists const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); @@ -829,8 +830,8 @@ export namespace Docs { let viewDoc: Doc; // determines whether viewDoc should be created using placeholder Doc or default if (placeholderDoc) { - placeholderDoc._height = Number(options._height); - placeholderDoc._width = Number(options._width); + placeholderDoc._height = options._height !== undefined ? Number(options._height) : undefined; + placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined; viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); } else { viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); @@ -846,6 +847,7 @@ export namespace Docs { updateCachedAcls(dataDoc); updateCachedAcls(viewDoc); + return viewDoc; } @@ -905,15 +907,8 @@ export namespace Docs { export function ColorDocument(options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - - // Mapping of all loading docs to files, i.e. keeps track of files being uploaded in current session - export const docsToFiles = new Map(); - export function LoadingDocument(file: File | string, options: DocumentOptions, ytString?: string) { - const fileName = typeof file == 'string' ? file : file.name; - options.title = fileName; - const loading = InstanceFromProto(Prototypes.get(DocumentType.LOADING), fileName, { _height: 150, _width: 200, ...options }); - return loading; + return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, ''); } export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { @@ -1756,6 +1751,7 @@ export namespace DocUtils { const full = { ...options, _width: 400, title: name }; const pathname = Utils.prepend(result.accessPaths.agnostic.client); const doc = await DocUtils.DocumentFromType(type, pathname, full, rootDoc); + rootDoc && (rootDoc.isLoading = undefined); if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 88ce457c6..50518eec1 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -84,6 +84,7 @@ export class KeyManager { }); private unmodified = action((keyname: string, e: KeyboardEvent) => { + const hasFffView = SelectionManager.Views().some(dv => dv.props.CollectionFreeFormDocumentView?.()); switch (keyname) { case 'u': if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { @@ -160,16 +161,16 @@ export class KeyManager { break; case 'arrowleft': UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge(-1, 0)), 'nudge left'); - return { stopPropagation: true, preventDefault: true }; + return { stopPropagation: hasFffView, preventDefault: hasFffView }; case 'arrowright': UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(1, 0)), 'nudge right'); - return { stopPropagation: true, preventDefault: true }; + return { stopPropagation: hasFffView, preventDefault: hasFffView }; case 'arrowup': UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -1)), 'nudge up'); - return { stopPropagation: true, preventDefault: true }; + return { stopPropagation: hasFffView, preventDefault: hasFffView }; case 'arrowdown': UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 1)), 'nudge down'); - return { stopPropagation: true, preventDefault: true }; + return { stopPropagation: hasFffView, preventDefault: hasFffView }; } return { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index e70cfb13c..4227955ef 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -464,14 +464,13 @@ export function CollectionSubView(moreProps?: X) { if (typeof files === 'string') { const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); - Docs.Create.docsToFiles.set(loading, files); + loading.isLoading = true; DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { generatedDocuments.push( ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); - // now that there is a doc do whatever slowload was going to do with that file - Docs.Create.docsToFiles.set(loading, file); + loading.isLoading = true; DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) @@ -516,5 +515,4 @@ import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes' import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { OverlayView } from '../OverlayView'; import { CollectionView, CollectionViewProps } from './CollectionView'; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index cfa4bb160..7907470dc 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -35,7 +35,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { export class CollectionFreeFormDocumentView extends DocComponent() { public static animFields: { key: string; val?: number }[] = [{ key: '_height' }, { key: '_width' }, { key: 'x' }, { key: 'y' }, { key: '_scrollTop' }, { key: 'opacity', val: 1 }, { key: 'viewScale', val: 1 }, { key: 'panX' }, { key: 'panY' }]; // fields that are configured to be animatable using animation frames public static animStringFields = ['backgroundColor', 'color']; // fields that are configured to be animatable using animation frames - public static animDataFields = ['data', 'text']; // fields that are configured to be animatable using animation frames + public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; get displayName() { @@ -121,7 +121,7 @@ export class CollectionFreeFormDocumentView extends DocComponent { + CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null); findexed?.length <= timecode + 1 && findexed.push(undefined as any); }); @@ -173,7 +173,7 @@ export class CollectionFreeFormDocumentView extends DocComponent (doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val))); CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode))); - CollectionFreeFormDocumentView.animDataFields.forEach(val => (Doc.GetProto(doc)[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', Doc.GetProto(doc), currTimecode))); + CollectionFreeFormDocumentView.animDataFields(doc).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'); diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 90bf90095..f3243f6cd 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -35,33 +35,14 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { return FieldView.LayoutString(LoadingBox, fieldKey); } - /** - * Returns true if file is uploading, false otherwise - */ - @computed private get isLoading(): boolean { - const file = Docs.Create.docsToFiles.get(this.rootDoc); - return file ? true : false; - } - - constructor(props: any) { - super(props); - } - render() { return (
- {this.isLoading ? ( -
-

Loading:

- {StrCast(this.rootDoc.title)} - -
- ) : ( -
-

Error Loading File:

- {StrCast(this.rootDoc.title)} -
- )} +
+

{this.rootDoc.isLoading ? 'Loading:' : 'Error Loading File:'}

+ {StrCast(this.rootDoc.title)} + {!this.rootDoc.isLoading ? null : } +
); } -- cgit v1.2.3-70-g09d2 From 5ad8289e8ca3b6ff8304ac4fa3c2d3adaa9abf6d Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Sep 2022 14:52:04 -0400 Subject: afixed loadingBox to show error message for unsupported videos. fixed video uploading to generate errors for unsupported (Chrome) codecs. fixed videos that don't have codec support to still play audio and show duration. fixed dragging rotated documents to show and place the rotted document WYSIWYG --- src/client/Network.ts | 97 +++++++------ src/client/documents/Documents.ts | 7 +- src/client/util/DragManager.ts | 44 ++++-- src/client/views/nodes/DocumentView.tsx | 4 +- src/client/views/nodes/LoadingBox.scss | 2 + src/client/views/nodes/LoadingBox.tsx | 4 +- src/client/views/nodes/VideoBox.tsx | 10 +- src/server/DashUploadUtils.ts | 236 +++++++++++++++++--------------- 8 files changed, 225 insertions(+), 179 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/Network.ts b/src/client/Network.ts index c781d4b6b..a222b320f 100644 --- a/src/client/Network.ts +++ b/src/client/Network.ts @@ -1,57 +1,54 @@ -import { Utils } from "../Utils"; +import { Utils } from '../Utils'; import requestPromise = require('request-promise'); -import { Upload } from "../server/SharedMediaTypes"; +import { Upload } from '../server/SharedMediaTypes'; export namespace Networking { + export async function FetchFromServer(relativeRoute: string) { + return (await fetch(relativeRoute)).text(); + } - export async function FetchFromServer(relativeRoute: string) { - return (await fetch(relativeRoute)).text(); - } + export async function PostToServer(relativeRoute: string, body?: any) { + const options = { + uri: Utils.prepend(relativeRoute), + method: 'POST', + body, + json: true, + }; + return requestPromise.post(options); + } - export async function PostToServer(relativeRoute: string, body?: any) { - const options = { - uri: Utils.prepend(relativeRoute), - method: "POST", - body, - json: true - }; - return requestPromise.post(options); - } - - /** - * Handles uploading basic file types to server and makes the API call to "/uploadFormData" endpoint - * with the mapping of GUID to filem as parameters. - * - * @param files the files to be uploaded to the server - * @returns the response as a json from the server - */ - export async function UploadFilesToServer(files: File | File[]): Promise[]> { - const formData = new FormData(); - if (Array.isArray(files)) { - if (!files.length) { - return []; - } - files.forEach(file => formData.append(Utils.GenerateGuid(), file)); - } else { - formData.append(Utils.GenerateGuid(), files); + /** + * Handles uploading basic file types to server and makes the API call to "/uploadFormData" endpoint + * with the mapping of GUID to filem as parameters. + * + * @param files the files to be uploaded to the server + * @returns the response as a json from the server + */ + export async function UploadFilesToServer(files: File | File[]): Promise[]> { + const formData = new FormData(); + if (Array.isArray(files)) { + if (!files.length) { + return []; } - const parameters = { - method: 'POST', - body: formData - }; - const response = await fetch("/uploadFormData", parameters); - return response.json(); - } - - export async function UploadYoutubeToServer(videoId: string): Promise[]> { - const parameters = { - method: 'POST', - body: JSON.stringify({ videoId }), - json: true - }; - const response = await fetch("/uploadYoutubeVideo", parameters); - return response.json(); - } - + files.forEach(file => formData.append(Utils.GenerateGuid(), file)); + } else { + formData.append(Utils.GenerateGuid(), files); + } + const parameters = { + method: 'POST', + body: formData, + }; + const response = await fetch('/uploadFormData', parameters); + return response.json(); + } -} \ No newline at end of file + export async function UploadYoutubeToServer(videoId: string): Promise[]> { + const parameters = { + method: 'POST', + body: JSON.stringify({ videoId }), + json: true, + }; + const response = await fetch('/uploadYoutubeVideo', parameters); + return response.json(); + } +} diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b22e16633..8c3b91177 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1849,7 +1849,12 @@ export namespace DocUtils { source: { name, type }, result, } = upfiles.lastElement(); - name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + if ((result as any).message) { + if (overwriteDoc) { + overwriteDoc.isLoading = false; + overwriteDoc.errorMessage = (result as any).message; + } + } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); }); } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 6386c87a0..dfd916e92 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -344,8 +344,7 @@ export namespace DragManager { } Object.assign(dragDiv.style, { width: '', height: '', overflow: '' }); dragDiv.hidden = false; - const scaleXs: number[] = [], - scaleYs: number[] = [], + const scalings: number[] = [], xs: number[] = [], ys: number[] = []; @@ -355,8 +354,15 @@ export namespace DragManager { top: Number.MAX_SAFE_INTEGER, bottom: Number.MIN_SAFE_INTEGER, }; + let rot = 0; const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : []; const dragElements = eles.map(ele => { + if (ele?.parentElement?.parentElement?.parentElement?.className === 'collectionFreeFormDocumentView-container') { + ele = ele.parentElement.parentElement.parentElement; + const rotStr = ele.style.transform.replace(/.*rotate\(([-0-9.]*)deg\).*/, '$1'); + if (rotStr) rot = Number(rotStr); + } + if (rot < 0) rot += 360; if (!ele.parentNode) dragDiv.appendChild(ele); const dragElement = ele.parentNode === dragDiv ? ele : (ele.cloneNode(true) as HTMLElement); const children = Array.from(dragElement.children); @@ -376,19 +382,29 @@ export namespace DragManager { } } const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / (ele.offsetWidth || rect.width); - const scaleY = scaleX; //ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX; + const rotWidth = (rot > 45 && rot < 135) || (rot > 215 && rot < 305) ? rect.height : rect.width; //rect.width * Math.cos((rot * Math.PI) / 180) + rect.height * Math.sin((rot * Math.PI) / 180); + const scaling = rot ? rotWidth / ele.offsetWidth : rect.width / (ele.offsetWidth || rect.width); 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 + (options?.offsetX || 0)); - ys.push(rect.top + (options?.offsetY || 0)); - scaleXs.push(scaleX); - scaleYs.push(scaleY); + const rotRad = (rot / 180) * Math.PI; + xs.push( + (rot > 90 && rot <= 270 ? rect.right : rect.left) + // + (rot > 270 ? -scaling * (ele.offsetHeight * Math.sin(rotRad)) : 0) + + (rot <= 90 || rot > 180 ? scaling * (ele.offsetHeight * Math.sin(rotRad)) : 0) + + (options?.offsetX || 0) + ); + ys.push( + rect.top + // + (rot > 180 ? -scaling * (ele.offsetWidth * Math.sin(rotRad)) : 0) + + (rot >= 90 && rot < 270 ? -scaling * (ele.offsetHeight * Math.cos(rotRad)) : 0) + + (options?.offsetY || 0) + ); + scalings.push(scaling); Object.assign(dragElement.style, { - opacity: '0.7', + opacity: '0', position: 'absolute', margin: '0', top: '0', @@ -399,9 +415,9 @@ export namespace DragManager { borderRadius: getComputedStyle(ele).borderRadius, zIndex: globalCssVariables.contextMenuZindex, transformOrigin: '0 0', - width: `${rect.width / scaleX}px`, - height: `${rect.height / scaleY}px`, - transform: `translate(${xs[0]}px, ${ys[0]}px) scale(${scaleX}, ${scaleY})`, + width: rot ? '' : `${rect.width / scaling}px`, + height: rot ? '' : `${rect.height / scaling}px`, + transform: `translate(${xs[0]}px, ${ys[0]}px) rotate(${rot}deg)`, }); dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; @@ -415,6 +431,8 @@ export namespace DragManager { [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none')); dragDiv.appendChild(dragElement); + scalings[scalings.length - 1] = rect.width / dragElement.getBoundingClientRect().width; + setTimeout(() => (dragElement.style.opacity = '0.7')); if (dragElement !== ele) { const children = [Array.from(ele.children), Array.from(dragElement.children)]; while (children[0].length) { @@ -542,7 +560,7 @@ export namespace DragManager { const moveVec = { x: x - lastPt.x, y: y - lastPt.y }; lastPt = { x, y }; - dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)); + dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot}deg) scale(${scalings[i]})`)); dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; }; const upHandler = (e: PointerEvent) => { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 01fadb48d..113574a64 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -498,8 +498,8 @@ export class DocumentViewInternal extends DocComponent() { render() { return ( -
+
-

{this.rootDoc.isLoading ? 'Loading:' : 'Error Loading File:'}

+

{this.rootDoc.isLoading ? 'Loading:' : StrCast(this.rootDoc.errorMessage, 'Error Loading File:')}

{StrCast(this.rootDoc.title)} {!this.rootDoc.isLoading ? null : }
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 0ff15f93b..6ff11258d 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -396,10 +396,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - const aspect = this.player!.videoWidth / this.player!.videoHeight; - Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth); - Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight); - this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect; + const aspect = this.player!.videoWidth / (this.player!.videoHeight || 1); + if (aspect) { + Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth); + Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight); + this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect; + } if (Number.isFinite(this.player!.duration)) { this.rawDuration = this.player!.duration; } else this.rawDuration = NumCast(this.dataDoc[this.fieldKey + '-duration']); diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index cae35da60..ef7192ecc 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -4,33 +4,33 @@ import * as exifr from 'exifr'; import { File } from 'formidable'; import { createWriteStream, existsSync, readFileSync, rename, unlinkSync, writeFile } from 'fs'; import * as path from 'path'; -import { basename } from "path"; +import { basename } from 'path'; import * as sharp from 'sharp'; import { Stream } from 'stream'; import { filesDirectory, publicDirectory } from '.'; import { Opt } from '../fields/Doc'; -import { ParsedPDF } from "../server/PdfTypes"; +import { ParsedPDF } from '../server/PdfTypes'; import { Utils } from '../Utils'; import { createIfNotExists } from './ActionUtilities'; import { clientPathToFile, Directory, pathToDirectory, serverPathToFile } from './ApiManagers/UploadManager'; -import { resolvedServerUrl } from "./server_Initialization"; +import { resolvedServerUrl } from './server_Initialization'; import { AcceptableMedia, Upload } from './SharedMediaTypes'; import request = require('request-promise'); import formidable = require('formidable'); import { file } from 'jszip'; import { csvParser } from './DataVizUtils'; -const { exec } = require("child_process"); +const { exec } = require('child_process'); const parse = require('pdf-parse'); -const ffmpeg = require("fluent-ffmpeg"); -const fs = require("fs"); -const requestImageSize = require("../client/util/request-image-size"); +const ffmpeg = require('fluent-ffmpeg'); +const fs = require('fs'); +const requestImageSize = require('../client/util/request-image-size'); export enum SizeSuffix { - Small = "_s", - Medium = "_m", - Large = "_l", - Original = "_o", - None = "" + Small = '_s', + Medium = '_m', + Large = '_l', + Original = '_o', + None = '', } export function InjectSize(filename: string, size: SizeSuffix) { @@ -43,7 +43,6 @@ function isLocal() { } export namespace DashUploadUtils { - export interface Size { width: number; suffix: SizeSuffix; @@ -59,19 +58,19 @@ export namespace DashUploadUtils { return AcceptableMedia.imageFormats.includes(path.extname(url).toLowerCase()); } - const size = "content-length"; - const type = "content-type"; + const size = 'content-length'; + const type = 'content-type'; + + const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; //TODO:glr - const { imageFormats, videoFormats, applicationFormats, audioFormats } = AcceptableMedia; //TODO:glr - export async function concatVideos(filePaths: string[]): Promise { // make a list of paths to create the ordered text file for ffmpeg const inputListName = 'concat.txt'; const textFilePath = path.join(filesDirectory, inputListName); // make a list of paths to create the ordered text file for ffmpeg - const filePathsText = filePaths.map(filePath => `file '${filePath}'`).join('\n'); + const filePathsText = filePaths.map(filePath => `file '${filePath}'`).join('\n'); // write the text file to the file system - writeFile(textFilePath, filePathsText, (err) => console.log(err)); + writeFile(textFilePath, filePathsText, err => console.log(err)); // make output file name based on timestamp const outputFileName = `output-${Utils.GenerateGuid()}.mp4`; @@ -81,87 +80,110 @@ export namespace DashUploadUtils { // concatenate the videos await new Promise((resolve, reject) => { var merge = ffmpeg(); - merge.input(textFilePath) - .inputOptions(['-f concat', '-safe 0']) + merge + .input(textFilePath) + .inputOptions(['-f concat', '-safe 0']) .outputOptions('-c copy') //.videoCodec("copy") .save(outputFilePath) - .on("error", reject) - .on("end", resolve); - }) - - // delete concat.txt from the file system - unlinkSync(textFilePath); - // delete the old segment videos from the server - filePaths.forEach(filePath => unlinkSync(filePath)); - - // return the path(s) to the output file - return { - accessPaths: getAccessPaths(Directory.videos, outputFileName) - } + .on('error', reject) + .on('end', resolve); + }); + + // delete concat.txt from the file system + unlinkSync(textFilePath); + // delete the old segment videos from the server + filePaths.forEach(filePath => unlinkSync(filePath)); + + // return the path(s) to the output file + return { + accessPaths: getAccessPaths(Directory.videos, outputFileName), + }; } export function uploadYoutube(videoId: string): Promise { - console.log("UPLOAD " + videoId); + console.log('UPLOAD ' + videoId); return new Promise>((res, rej) => { - exec('youtube-dl -o ' + (videoId + ".mp4") + ' https://www.youtube.com/watch?v=' + videoId + ' -f "best[filesize<50M]"', - (error: any, stdout: any, stderr: any) => { - if (error) console.log(`error: ${error.message}`); - else if (stderr) console.log(`stderr: ${stderr}`); - else { - console.log(`stdout: ${stdout}`); - const data = { size: 0, path: videoId + ".mp4", name: videoId, type: "video/mp4" }; - const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ""), mtime: null, length: 0, mime: "", toJson: () => undefined as any }) }; - res(MoveParsedFile(file, Directory.videos)); - } - }); + exec('youtube-dl -o ' + (videoId + '.mp4') + ' https://www.youtube.com/watch?v=' + videoId + ' -f "best[filesize<50M]"', (error: any, stdout: any, stderr: any) => { + if (error) console.log(`error: ${error.message}`); + else if (stderr) console.log(`stderr: ${stderr}`); + else { + console.log(`stdout: ${stdout}`); + const data = { size: 0, path: videoId + '.mp4', name: videoId, type: 'video/mp4' }; + const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: null, length: 0, mime: '', toJson: () => undefined as any }) }; + res(MoveParsedFile(file, Directory.videos)); + } + }); }); } export async function upload(file: File): Promise { const { type, path, name } = file; - const types = type?.split("/") ?? []; + const types = type?.split('/') ?? []; const category = types[0]; let format = `.${types[1]}`; console.log(green(`Processing upload of file (${name}) and format (${format}) with upload type (${type}) in category (${category}).`)); - + switch (category) { - case "image": + case 'image': if (imageFormats.includes(format)) { const result = await UploadImage(path, basename(path)); return { source: file, result }; } - case "video": - if (format.includes("x-matroska")) { - console.log("case video"); - await new Promise(res => ffmpeg(file.path) - .videoCodec("copy") // this will copy the data instead of reencode it - .save(file.path.replace(".mkv", ".mp4")) - .on('end', res)); - file.path = file.path.replace(".mkv", ".mp4"); - format = ".mp4"; + case 'video': + if (format.includes('x-matroska')) { + console.log('case video'); + await new Promise(res => + ffmpeg(file.path) + .videoCodec('copy') // this will copy the data instead of reencode it + .save(file.path.replace('.mkv', '.mp4')) + .on('end', res) + ); + file.path = file.path.replace('.mkv', '.mp4'); + format = '.mp4'; + } + if (format.includes('quicktime')) { + let abort = false; + await new Promise(res => + ffmpeg.ffprobe(file.path, (err: any, metadata: any) => { + if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) { + abort = true; + } + res(); + }) + ); + if (abort) return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${name}). Please convert to an .mp4` } }; + // bcz: instead of aborting, we could convert the file using the code below to an mp4. Problem is that this takes a long time and will clog up the server. + // await new Promise(res => + // ffmpeg(file.path) + // .videoCodec('libx264') // this will copy the data instead of reencode it + // .audioCodec('mp2') + // .save(file.path.replace('.MOV', '.mp4').replace('.mov', '.mp4')) + // .on('end', res) + // ); + // file.path = file.path.replace('.mov', '.mp4').replace('.MOV', '.mp4'); + // format = '.mp4'; } if (videoFormats.includes(format)) { return MoveParsedFile(file, Directory.videos); } - case "application": + case 'application': if (applicationFormats.includes(format)) { return UploadPdf(file); } - case "audio": - const components = format.split(";"); + case 'audio': + const components = format.split(';'); if (components.length > 1) { format = components[0]; } if (audioFormats.includes(format)) { return UploadAudio(file, format); } - case "text": - if (types[1] == "csv") { + case 'text': + if (types[1] == 'csv') { return UploadCsv(file); } - } console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`)); @@ -176,22 +198,21 @@ export namespace DashUploadUtils { const name = path.basename(sourcePath); const textFilename = `${name.substring(0, name.length - 4)}.txt`; const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename)); - writeStream.write(result.text, error => error ? reject(error) : resolve()); + writeStream.write(result.text, error => (error ? reject(error) : resolve())); }); return MoveParsedFile(file, Directory.pdfs, undefined, result.text); } async function UploadCsv(file: File) { - const { path: sourcePath } = file; - // read the file as a string + const { path: sourcePath } = file; + // read the file as a string const data = readFileSync(sourcePath, 'utf8'); // split the string into an array of lines return MoveParsedFile(file, Directory.csv, undefined, data); // console.log(csvParser(data)); - } - const manualSuffixes = [".webm"]; + const manualSuffixes = ['.webm']; async function UploadAudio(file: File, format: string) { const suffix = manualSuffixes.includes(format) ? format : undefined; @@ -200,22 +221,22 @@ export namespace DashUploadUtils { /** * Uploads an image specified by the @param source to Dash's /public/files/ - * directory, and returns information generated during that upload - * + * directory, and returns information generated during that upload + * * @param {string} source is either the absolute path of an already uploaded image or * the url of a remote image * @param {string} filename dictates what to call the image. If not specified, * the name {@param prefix}_upload_{GUID} * @param {string} prefix is a string prepended to the generated image name in the * event that @param filename is not specified - * + * * @returns {ImageUploadInformation | Error} This method returns * 1) the paths to the uploaded images (plural due to resizing) * 2) the exif data embedded in the image, or the error explaining why exif couldn't be parsed * 3) the size of the image, in bytes (4432130) * 4) the content type of the image, i.e. image/(jpeg | png | ...) */ - export const UploadImage = async (source: string, filename?: string, prefix: string = ""): Promise => { + export const UploadImage = async (source: string, filename?: string, prefix: string = ''): Promise => { const metadata = await InspectImage(source); if (metadata instanceof Error) { return metadata; @@ -225,12 +246,12 @@ export namespace DashUploadUtils { export async function buildFileDirectories() { if (!existsSync(publicDirectory)) { - console.error("\nPlease ensure that the following directory exists...\n"); + console.error('\nPlease ensure that the following directory exists...\n'); console.log(publicDirectory); process.exit(0); } if (!existsSync(filesDirectory)) { - console.error("\nPlease ensure that the following directory exists...\n"); + console.error('\nPlease ensure that the following directory exists...\n'); console.log(filesDirectory); process.exit(0); } @@ -252,7 +273,7 @@ export namespace DashUploadUtils { /** * Based on the url's classification as local or remote, gleans * as much information as possible about the specified image - * + * * @param source is the path or url to the image in question */ export const InspectImage = async (source: string): Promise => { @@ -265,9 +286,9 @@ export namespace DashUploadUtils { */ if ((rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source)) !== null) { const [ext, data] = rawMatches.slice(1, 3); - const resolved = filename = `upload_${Utils.GenerateGuid()}.${ext}`; + const resolved = (filename = `upload_${Utils.GenerateGuid()}.${ext}`); const error = await new Promise(resolve => { - writeFile(serverPathToFile(Directory.images, resolved), data, "base64", resolve); + writeFile(serverPathToFile(Directory.images, resolved), data, 'base64', resolve); }); if (error !== null) { return error; @@ -276,12 +297,12 @@ export namespace DashUploadUtils { } let resolvedUrl: string; /** - * + * * At this point, we want to take whatever url we have and make sure it's requestable. * Anything that's hosted by some other website already is, but if the url is a local file url * (locates the file on this server machine), we have to resolve the client side url by cutting out the * basename subtree (i.e. /images/.) and put it on the end of the server's url. - * + * * This can always be localhost, regardless of whether this is on the server or not, since we (the server, not the client) * will be the ones making the request, and from the perspective of dash-release or dash-web, localhost: refers to the same thing * as the full dash-release.eastus.cloudapp.azure.com:. @@ -290,18 +311,18 @@ export namespace DashUploadUtils { if (matches === null) { resolvedUrl = source; } else { - resolvedUrl = `${resolvedServerUrl}/${matches[1].split("\\").join("/")}`; + resolvedUrl = `${resolvedServerUrl}/${matches[1].split('\\').join('/')}`; } // See header comments: not all image files have exif data (I believe only JPG is the only format that can have it) const exifData = await parseExifData(resolvedUrl); const results = { exifData, - requestable: resolvedUrl + requestable: resolvedUrl, }; // Use the request library to parse out file level image information in the headers - const { headers } = (await new Promise((resolve, reject) => { - request.head(resolvedUrl, (error, res) => error ? reject(error) : resolve(res)); - }).catch(console.error)); + const { headers } = await new Promise((resolve, reject) => { + request.head(resolvedUrl, (error, res) => (error ? reject(error) : resolve(res))); + }).catch(console.error); try { // Compute the native width and height ofthe image with an npm module const { width: nativeWidth, height: nativeHeight } = await requestImageSize(resolvedUrl); @@ -313,7 +334,7 @@ export namespace DashUploadUtils { nativeWidth, nativeHeight, filename, - ...results + ...results, }; } catch (e: any) { console.log(e); @@ -340,12 +361,14 @@ export namespace DashUploadUtils { rename(sourcePath, destinationPath, error => { resolve({ source: file, - result: error ? error : { - accessPaths: { - agnostic: getAccessPaths(destination, name) - }, - rawText: text - } + result: error + ? error + : { + accessPaths: { + agnostic: getAccessPaths(destination, name), + }, + rawText: text, + }, }); }); }); @@ -354,19 +377,19 @@ export namespace DashUploadUtils { export function getAccessPaths(directory: Directory, fileName: string) { return { client: clientPathToFile(directory, fileName), - server: serverPathToFile(directory, fileName) + server: serverPathToFile(directory, fileName), }; } - export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename?: string, prefix = "", cleanUp = true): Promise => { + export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename?: string, prefix = '', cleanUp = true): Promise => { const { requestable, source, ...remaining } = metadata; - const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split("/")[1].toLowerCase()}`; + const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split('/')[1].toLowerCase()}`; const { images } = Directory; const information: Upload.ImageInformation = { accessPaths: { - agnostic: getAccessPaths(images, resolved) + agnostic: getAccessPaths(images, resolved), }, - ...metadata + ...metadata, }; const writtenFiles = await outputResizedImages(() => request(requestable), resolved, pathToDirectory(Directory.images)); for (const suffix of Object.keys(writtenFiles)) { @@ -383,9 +406,9 @@ export namespace DashUploadUtils { const val: any = layer[key]; if (val instanceof Buffer) { layer[key] = val.toString(); - } else if (Array.isArray(val) && typeof val[0] === "number") { + } else if (Array.isArray(val) && typeof val[0] === 'number') { layer[key] = Buffer.from(val).toString(); - } else if (typeof val === "object") { + } else if (typeof val === 'object') { bufferConverterRec(val); } } @@ -410,20 +433,20 @@ export namespace DashUploadUtils { const pngOptions = { compressionLevel: 9, adaptiveFiltering: true, - force: true + force: true, }; export async function outputResizedImages(streamProvider: () => Stream | Promise, outputFileName: string, outputDirectory: string) { const writtenFiles: { [suffix: string]: string } = {}; for (const { resizer, suffix } of resizers(path.extname(outputFileName))) { - const outputPath = path.resolve(outputDirectory, writtenFiles[suffix] = InjectSize(outputFileName, suffix)); + const outputPath = path.resolve(outputDirectory, (writtenFiles[suffix] = InjectSize(outputFileName, suffix))); await new Promise(async (resolve, reject) => { const source = streamProvider(); let readStream: Stream = source instanceof Promise ? await source : source; if (resizer) { readStream = readStream.pipe(resizer.withMetadata()); } - readStream.pipe(createWriteStream(outputPath)).on("close", resolve).on("error", reject); + readStream.pipe(createWriteStream(outputPath)).on('close', resolve).on('error', reject); }); } return writtenFiles; @@ -442,15 +465,14 @@ export namespace DashUploadUtils { initial = initial.webp(); } else if (tiffs.includes(ext)) { initial = initial.tiff(); - } else if (ext === ".gif") { + } else if (ext === '.gif') { initial = undefined; } return { resizer: initial, - suffix + suffix, }; - }) + }), ]; } - -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From e70360946815cdcde434e25eb592e1b919bb4105 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 6 Sep 2022 14:06:02 -0400 Subject: final cleanup of dragging rotated images. fixed loading of youtube videos and displaying errors when the upload fails --- src/client/documents/Documents.ts | 13 +- src/client/util/DragManager.ts | 39 ++-- src/client/views/PreviewCursor.tsx | 55 ++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 12 +- src/client/views/nodes/LoadingBox.tsx | 10 +- src/client/views/nodes/VideoBox.scss | 20 +- src/client/views/nodes/VideoBox.tsx | 212 ++++++++++----------- src/server/ApiManagers/UploadManager.ts | 2 +- src/server/DashUploadUtils.ts | 48 +++-- src/server/SharedMediaTypes.ts | 38 ++-- 11 files changed, 257 insertions(+), 194 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8c3b91177..7111cb233 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,5 +1,4 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { files } from 'jszip'; import { action, runInAction } from 'mobx'; import { basename } from 'path'; import { DateField } from '../../fields/DateField'; @@ -1780,6 +1779,9 @@ export namespace DocUtils { proto.lng = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); } } + if (Upload.isVideoInformation(result)) { + proto['data-duration'] = result.duration; + } generatedDocuments.push(doc); } } @@ -1814,7 +1816,7 @@ export namespace DocUtils { source: { name, type }, result, } of await Networking.UploadYoutubeToServer(videoId)) { - name && type && processFileupload(generatedDocuments, name, type, result, options); + name && processFileupload(generatedDocuments, name, type, result, options); } return generatedDocuments; } @@ -1826,7 +1828,12 @@ export namespace DocUtils { source: { name, type }, result, } = upfiles.lastElement(); - name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + if ((result as any).message) { + if (overwriteDoc) { + overwriteDoc.isLoading = false; + overwriteDoc.errorMessage = (result as any).message; + } + } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); }); } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index dfd916e92..cec158d23 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -382,29 +382,28 @@ export namespace DragManager { } } const rect = ele.getBoundingClientRect(); - const rotWidth = (rot > 45 && rot < 135) || (rot > 215 && rot < 305) ? rect.height : rect.width; //rect.width * Math.cos((rot * Math.PI) / 180) + rect.height * Math.sin((rot * Math.PI) / 180); - const scaling = rot ? rotWidth / ele.offsetWidth : rect.width / (ele.offsetWidth || rect.width); + const w = ele.offsetWidth || rect.width; + const h = ele.offsetHeight || rect.height; + const rotR = -(rot / 180) * Math.PI; + const tl = [0, 0]; + const tr = [Math.cos(rotR) * w, Math.sin(-rotR) * w]; + const bl = [Math.sin(rotR) * h, Math.cos(-rotR) * h]; + const br = [Math.cos(rotR) * w + Math.sin(rotR) * h, Math.cos(-rotR) * h - Math.sin(rotR) * w]; + const minx = Math.min(tl[0], tr[0], br[0], bl[0]); + const maxx = Math.max(tl[0], tr[0], br[0], bl[0]); + const miny = Math.min(tl[1], tr[1], br[1], bl[1]); + const maxy = Math.max(tl[1], tr[1], br[1], bl[1]); + const scaling = rect.width / (Math.abs(maxx - minx) || 1); 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); - const rotRad = (rot / 180) * Math.PI; - xs.push( - (rot > 90 && rot <= 270 ? rect.right : rect.left) + // - (rot > 270 ? -scaling * (ele.offsetHeight * Math.sin(rotRad)) : 0) + - (rot <= 90 || rot > 180 ? scaling * (ele.offsetHeight * Math.sin(rotRad)) : 0) + - (options?.offsetX || 0) - ); - ys.push( - rect.top + // - (rot > 180 ? -scaling * (ele.offsetWidth * Math.sin(rotRad)) : 0) + - (rot >= 90 && rot < 270 ? -scaling * (ele.offsetHeight * Math.cos(rotRad)) : 0) + - (options?.offsetY || 0) - ); + xs.push(((0 - minx) / (maxx - minx)) * rect.width + rect.left); + ys.push(((0 - miny) / (maxy - miny)) * rect.height + rect.top); scalings.push(scaling); Object.assign(dragElement.style, { - opacity: '0', + opacity: '0.7', position: 'absolute', margin: '0', top: '0', @@ -415,9 +414,9 @@ export namespace DragManager { borderRadius: getComputedStyle(ele).borderRadius, zIndex: globalCssVariables.contextMenuZindex, transformOrigin: '0 0', - width: rot ? '' : `${rect.width / scaling}px`, - height: rot ? '' : `${rect.height / scaling}px`, - transform: `translate(${xs[0]}px, ${ys[0]}px) rotate(${rot}deg)`, + width: '', + height: '', + transform: `translate(${xs[0]}px, ${ys[0]}px) rotate(${rot}deg) scale(${scaling})`, }); dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; @@ -431,8 +430,6 @@ export namespace DragManager { [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none')); dragDiv.appendChild(dragElement); - scalings[scalings.length - 1] = rect.width / dragElement.getBoundingClientRect().width; - setTimeout(() => (dragElement.style.opacity = '0.7')); if (dragElement !== ele) { const children = [Array.from(ele.children), Array.from(dragElement.children)]; while (children[0].length) { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 68f5f072d..4c17d5a97 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -4,9 +4,9 @@ import 'normalize.css'; import * as React from 'react'; import { Doc } from '../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../fields/Types'; -import { returnFalse } from '../../Utils'; +import { emptyFunction, returnFalse } from '../../Utils'; import { DocServer } from '../DocServer'; -import { Docs, DocUtils } from '../documents/Documents'; +import { Docs, DocumentOptions, DocUtils } from '../documents/Documents'; import { Transform } from '../util/Transform'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; @@ -16,15 +16,25 @@ import './PreviewCursor.scss'; export class PreviewCursor extends React.Component<{}> { static _onKeyPress?: (e: KeyboardEvent) => void; static _getTransform: () => Transform; - static _addDocument: (doc: Doc | Doc[]) => void; + static _addDocument: (doc: Doc | Doc[]) => boolean; static _addLiveTextDoc: (doc: Doc) => void; static _nudge?: undefined | ((x: number, y: number) => boolean); + static _slowLoadDocuments?: ( + files: File[] | string, + options: DocumentOptions, + generatedDocuments: Doc[], + text: string, + completed: ((doc: Doc[]) => void) | undefined, + clientX: number, + clientY: number, + addDocument: (doc: Doc | Doc[]) => boolean + ) => Promise; @observable static _clickPoint = [0, 0]; @observable public static Visible = false; constructor(props: any) { super(props); document.addEventListener('keydown', this.onKeyPress); - document.addEventListener('paste', this.paste); + document.addEventListener('paste', this.paste, true); } paste = async (e: ClipboardEvent) => { @@ -38,20 +48,16 @@ export class PreviewCursor extends React.Component<{}> { if (plain) { // tests for youtube and makes video document if (plain.indexOf('www.youtube.com/watch') !== -1) { - const url = plain.replace('youtube.com/watch?v=', 'youtube.com/embed/'); - undoBatch(() => - PreviewCursor._addDocument( - Docs.Create.VideoDocument(url, { - title: url, - _width: 400, - _height: 315, - _nativeWidth: 600, - _nativeHeight: 472.5, - x: newPoint[0], - y: newPoint[1], - }) - ) - )(); + const batch = UndoManager.StartBatch('youtube upload'); + const generatedDocuments: Doc[] = []; + const options = { + title: plain, + _width: 400, + _height: 315, + x: newPoint[0], + y: newPoint[1], + }; + PreviewCursor._slowLoadDocuments?.(plain.split('v=')[1].split('&')[0], options, generatedDocuments, '', undefined, newPoint[0], newPoint[1], PreviewCursor._addDocument).then(batch.end); } else if (re.test(plain)) { const url = plain; undoBatch(() => @@ -184,7 +190,17 @@ export class PreviewCursor extends React.Component<{}> { addLiveText: (doc: Doc) => void, getTransform: () => Transform, addDocument: undefined | ((doc: Doc | Doc[]) => boolean), - nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean) + nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean), + slowLoadDocuments: ( + files: File[] | string, + options: DocumentOptions, + generatedDocuments: Doc[], + text: string, + completed: ((doc: Doc[]) => void) | undefined, + clientX: number, + clientY: number, + addDocument: (doc: Doc | Doc[]) => boolean + ) => Promise ) { this._clickPoint = [x, y]; this._onKeyPress = onKeyPress; @@ -192,6 +208,7 @@ export class PreviewCursor extends React.Component<{}> { this._getTransform = getTransform; this._addDocument = addDocument || returnFalse; this._nudge = nudge; + this._slowLoadDocuments = slowLoadDocuments; this.Visible = true; } render() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0c4de681a..947bd8aaa 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -52,7 +52,6 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import React = require('react'); -import e = require('connect-flash'); export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -1858,6 +1857,7 @@ export class CollectionFreeFormView extends CollectionSubView 0 ? undefined : this.nudge} addDocTab={this.addDocTab} + slowLoadDocuments={this.slowLoadDocuments} trySelectCluster={this.trySelectCluster} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 65a11cbcb..58a00bbac 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -40,6 +40,16 @@ interface MarqueeViewProps { nudge?: (x: number, y: number, nudgeTime?: number) => boolean; ungroup?: () => void; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; + slowLoadDocuments: ( + files: File[] | string, + options: DocumentOptions, + generatedDocuments: Doc[], + text: string, + completed: ((doc: Doc[]) => void) | undefined, + clientX: number, + clientY: number, + addDocument: (doc: Doc | Doc[]) => boolean + ) => Promise; } export interface MarqueeViewBounds { @@ -330,7 +340,7 @@ export class MarqueeView extends React.Component() { return (
-

{this.rootDoc.isLoading ? 'Loading:' : StrCast(this.rootDoc.errorMessage, 'Error Loading File:')}

+

{this.rootDoc.isLoading ? 'Loading (can take several minutes):' : StrCast(this.rootDoc.errorMessage, 'Error Loading File:')}

{StrCast(this.rootDoc.title)} {!this.rootDoc.isLoading ? null : }
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index aa51714da..c2aee7a1b 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -1,4 +1,4 @@ -@import "../global/globalCssVariables.scss"; +@import '../global/globalCssVariables.scss'; .mini-viewer { cursor: grab; @@ -97,7 +97,7 @@ height: 40px; padding: 0 10px 0 7px; transition: opacity 0.3s; - z-index: 100001; + z-index: 10001; .timecode-controls { display: flex; @@ -114,7 +114,8 @@ } } - .toolbar-slider.volume, .toolbar-slider.zoom { + .toolbar-slider.volume, + .toolbar-slider.zoom { width: 50px; } @@ -157,7 +158,8 @@ } } -.videoBox-content-fullScreen, .videoBox-content-fullScreen-interactive { +.videoBox-content-fullScreen, +.videoBox-content-fullScreen-interactive { display: flex; justify-content: center; align-items: flex-end; @@ -175,16 +177,16 @@ video::-webkit-media-controls { display: none !important; } -input[type="range"] { +input[type='range'] { -webkit-appearance: none; background: none; } -input[type="range"]:focus { +input[type='range']:focus { outline: none; } -input[type="range"]::-webkit-slider-runnable-track { +input[type='range']::-webkit-slider-runnable-track { width: 100%; height: 10px; cursor: pointer; @@ -193,7 +195,7 @@ input[type="range"]::-webkit-slider-runnable-track { border-radius: 10px; } -input[type="range"]::-webkit-slider-thumb { +input[type='range']::-webkit-slider-thumb { box-shadow: 0; border: 0; height: 12px; @@ -203,4 +205,4 @@ input[type="range"]::-webkit-slider-thumb { cursor: pointer; -webkit-appearance: none; margin-top: -1px; -} \ No newline at end of file +} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 6ff11258d..bfb8c1528 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -935,112 +935,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent -
- -
- - {this.timeline && width > 150 && ( -
-
{formatTime(curTime)}
- - {this._fullScreen || (this.heightPercent === 100 && width > 200) ? ( -
- { - e.stopPropagation(); - this._scrubbing = true; - })} - onChange={(e: React.ChangeEvent) => this.setPlayheadTime(Number(e.target.value))} - onPointerUp={action((e: React.PointerEvent) => { - e.stopPropagation(); - this._scrubbing = false; - })} - /> -
- ) : ( -
/
- )} - -
{formatTime(this.timeline.clipDuration)}
-
- )} - -
- -
- - {!this._fullScreen && width > 300 && ( -
- -
- )} - - {!this._fullScreen && width > 300 && ( -
- -
- )} - -
{ - e.stopPropagation(); - this.toggleMute(); - }}> - -
- {width > 300 && ( - e.stopPropagation()} - onChange={(e: React.ChangeEvent) => this.setVolume(Number(e.target.value))} - /> - )} - - {!this._fullScreen && this.heightPercent !== 100 && width > 300 && ( - <> -
- -
- { - e.stopPropagation(); - }} - onChange={(e: React.ChangeEvent) => { - this.zoom(Number(e.target.value)); - }} - /> - - )} - - ); - } - // renders CollectionStackedTimeline @computed get renderTimeline() { return ( @@ -1149,6 +1043,112 @@ export class VideoBox extends ViewBoxAnnotatableComponent ); } + + @computed get UIButtons() { + const bounds = this.props.docViewPath().lastElement().getBounds(); + const width = (bounds?.right || 0) - (bounds?.left || 0); + const curTime = NumCast(this.layoutDoc._currentTimecode) - (this.timeline?.clipStart || 0); + return ( + <> +
+ +
+ + {this.timeline && width > 150 && ( +
+
{formatTime(curTime)}
+ + {this._fullScreen || (this.heightPercent === 100 && width > 200) ? ( +
+ { + e.stopPropagation(); + this._scrubbing = true; + })} + onChange={(e: React.ChangeEvent) => this.setPlayheadTime(Number(e.target.value))} + onPointerUp={action((e: React.PointerEvent) => { + e.stopPropagation(); + this._scrubbing = false; + })} + /> +
+ ) : ( +
/
+ )} + +
{formatTime(this.timeline.clipDuration)}
+
+ )} + +
+ +
+ + {!this._fullScreen && width > 300 && ( +
+ +
+ )} + + {!this._fullScreen && width > 300 && ( +
+ +
+ )} + +
{ + e.stopPropagation(); + this.toggleMute(); + }}> + +
+ {width > 300 && ( + e.stopPropagation()} + onChange={(e: React.ChangeEvent) => this.setVolume(Number(e.target.value))} + /> + )} + + {!this._fullScreen && this.heightPercent !== 100 && width > 300 && ( + <> +
+ +
+ { + e.stopPropagation(); + }} + onChange={(e: React.ChangeEvent) => { + this.zoom(Number(e.target.value)); + }} + /> + + )} + + ); + } } VideoBox._nativeControls = false; diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 787e331c5..0b6e18743 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -86,7 +86,7 @@ export default class UploadManager extends ApiManager { const videoId = JSON.parse(payload).videoId; const results: Upload.FileResponse[] = []; const result = await DashUploadUtils.uploadYoutube(videoId); - result && !(result.result instanceof Error) && results.push(result); + result && results.push(result); _success(res, results); resolve(); }); diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index ef7192ecc..28e26e51e 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -17,8 +17,6 @@ import { resolvedServerUrl } from './server_Initialization'; import { AcceptableMedia, Upload } from './SharedMediaTypes'; import request = require('request-promise'); import formidable = require('formidable'); -import { file } from 'jszip'; -import { csvParser } from './DataVizUtils'; const { exec } = require('child_process'); const parse = require('pdf-parse'); const ffmpeg = require('fluent-ffmpeg'); @@ -102,16 +100,41 @@ export namespace DashUploadUtils { } export function uploadYoutube(videoId: string): Promise { - console.log('UPLOAD ' + videoId); return new Promise>((res, rej) => { - exec('youtube-dl -o ' + (videoId + '.mp4') + ' https://www.youtube.com/watch?v=' + videoId + ' -f "best[filesize<50M]"', (error: any, stdout: any, stderr: any) => { - if (error) console.log(`error: ${error.message}`); - else if (stderr) console.log(`stderr: ${stderr}`); - else { - console.log(`stdout: ${stdout}`); - const data = { size: 0, path: videoId + '.mp4', name: videoId, type: 'video/mp4' }; - const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: null, length: 0, mime: '', toJson: () => undefined as any }) }; - res(MoveParsedFile(file, Directory.videos)); + console.log('Uploading YouTube video: ' + videoId); + exec('youtube-dl -o ' + (videoId + '.mp4') + ' ' + videoId + ' -f "bestvideo[filesize<5M]+bestaudio/bestvideo+bestaudio"', (error: any, stdout: any, stderr: any) => { + if (error) { + console.log(`error: Error: ${error.message}`); + res({ + source: { + size: 0, + path: videoId, + name: videoId, + type: '', + toJSON: () => ({ name: videoId, path: videoId }), + }, + result: { name: 'failed youtube query', message: `Could not upload YouTube video (${videoId}). Error: ${error.message}` }, + }); + } else if (stderr) { + console.log(`stderr: StdError: ${stderr}`); + res({ + source: { + size: 0, + path: videoId, + name: videoId, + type: '', + toJSON: () => ({ name: videoId, path: videoId }), + }, + result: { name: 'failed youtube query', message: `Could not upload YouTube video (${videoId}). Error: ${stderr}` }, + }); + } else { + exec('youtube-dl -o ' + (videoId + '.mp4') + ' ' + videoId + ' --get-duration', (error: any, stdout: any, stderr: any) => { + const time = Array.from(stdout.trim().split(':')).reverse(); + const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0); + const data = { size: 0, path: videoId + '.mp4', name: videoId, type: 'video/mp4' }; + const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: duration.toString(), mime: '', toJson: () => undefined as any }) }; + res(MoveParsedFile(file, Directory.videos)); + }); } }); }); @@ -352,7 +375,7 @@ export namespace DashUploadUtils { * @param suffix If the file doesn't have a suffix and you want to provide it one * to appear in the new location */ - export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string): Promise { + export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string, duration?: number): Promise { const { path: sourcePath } = file; let name = path.basename(sourcePath); suffix && (name += suffix); @@ -368,6 +391,7 @@ export namespace DashUploadUtils { agnostic: getAccessPaths(destination, name), }, rawText: text, + duration, }, }); }); diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index cde95526f..7db1c2dae 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -2,36 +2,45 @@ import { ExifData } from 'exif'; import { File } from 'formidable'; export namespace AcceptableMedia { - export const gifs = [".gif"]; - export const pngs = [".png"]; - export const jpgs = [".jpg", ".jpeg"]; - export const webps = [".webp"]; - export const tiffs = [".tiff"]; + export const gifs = ['.gif']; + export const pngs = ['.png']; + export const jpgs = ['.jpg', '.jpeg']; + export const webps = ['.webp']; + export const tiffs = ['.tiff']; export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; - export const videoFormats = [".mov", ".mp4", ".quicktime", ".mkv", ".x-matroska;codecs=avc1"]; - export const applicationFormats = [".pdf"]; - export const audioFormats = [".wav", ".mp3", ".mpeg", ".flac", ".au", ".aiff", ".m4a", ".webm"]; + export const videoFormats = ['.mov', '.mp4', '.quicktime', '.mkv', '.x-matroska;codecs=avc1']; + export const applicationFormats = ['.pdf']; + export const audioFormats = ['.wav', '.mp3', '.mpeg', '.flac', '.au', '.aiff', '.m4a', '.webm']; } export namespace Upload { - export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation { - return "nativeWidth" in uploadResponse; + return 'nativeWidth' in uploadResponse; + } + + export function isVideoInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.VideoInformation { + return 'duration' in uploadResponse; } export interface FileInformation { accessPaths: AccessPathInfo; rawText?: string; + duration?: number; } - export type FileResponse = { source: File, result: T | Error }; + export type FileResponse = { source: File; result: T | Error }; export type ImageInformation = FileInformation & InspectionResults; + export type VideoInformation = FileInformation & VideoResults; + export interface AccessPathInfo { - [suffix: string]: { client: string, server: string }; + [suffix: string]: { client: string; server: string }; } + export interface VideoResults { + duration: number; + } export interface InspectionResults { source: string; requestable: string; @@ -44,8 +53,7 @@ export namespace Upload { } export interface EnrichedExifData { - data: ExifData & ExifData["gps"]; + data: ExifData & ExifData['gps']; error?: string; } - -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From 2c5942d76ad6e9b5874b98658b7c5af59cdfa367 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 8 Sep 2022 14:38:38 -0400 Subject: fixed font menu settings to be accurate of current selection. --- src/client/documents/Documents.ts | 16 ++++-- src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/nodes/button/FontIconBox.tsx | 36 ++++--------- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- .../views/nodes/formattedText/RichTextMenu.tsx | 62 +++++++++------------- 5 files changed, 49 insertions(+), 73 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7111cb233..57a24b304 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1386,11 +1386,17 @@ export namespace DocUtils { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript( - scripts[key], - { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name }, - { _readOnly_: true } - ); + doc[key] = ScriptField.MakeScript(scripts[key], { + dragData: DragManager.DocumentDragData.name, + value: 'any', + _readOnly_: 'boolean', + scriptContext: 'any', + thisContainer: Doc.name, + documentView: Doc.name, + heading: Doc.name, + checked: 'boolean', + containingTreeView: Doc.name, + }); } }); funcs && diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 25852c6ce..9d3f19e50 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -623,8 +623,8 @@ export class CurrentUserUtils { return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'}, btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, - { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, - { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_); }'}}, + { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, + { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_);}'}}, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} }, diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index c72b5ca9b..4a6099fb3 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -576,27 +576,19 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { // toggle: Set overlay status of selected document ScriptingGlobals.add(function setFont(font: string, checkResult?: boolean) { - SelectionManager.Docs().map(doc => (doc._fontFamily = font)); - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (checkResult) { - return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - } - if (editorView) RichTextMenu.Instance.setFontFamily(font); - else Doc.UserDoc().fontFamily = font; + if (checkResult) return RichTextMenu.Instance?.fontFamily; + font && RichTextMenu.Instance.setFontFamily(font); }); ScriptingGlobals.add(function getActiveTextInfo(info: 'family' | 'size' | 'color' | 'highlight') { const editorView = RichTextMenu.Instance.TextView?.EditorView; const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection(); + // prettier-ignore switch (info) { - case 'family': - return style?.activeFamilies[0]; - case 'size': - return style?.activeSizes[0]; - case 'color': - return style?.activeColors[0]; - case 'highlight': - return style?.activeHighlights[0]; + case 'family': return style?.activeFamilies[0]; + case 'size': return style?.activeSizes[0]; + case 'color': return style?.activeColors[0]; + case 'highlight': return style?.activeHighlights[0]; } }); @@ -621,14 +613,8 @@ ScriptingGlobals.add(function setBulletList(mapStyle: 'bullet' | 'decimal', chec // toggle: Set overlay status of selected document ScriptingGlobals.add(function setFontColor(color?: string, checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - - if (checkResult) { - return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor; - } - - if (editorView) color && RichTextMenu.Instance.setColor(color, editorView, editorView?.dispatch); - else Doc.UserDoc().fontColor = color; + if (checkResult) return RichTextMenu.Instance.fontColor; + color && RichTextMenu.Instance.setColor(color); }); // toggle: Set overlay status of selected document @@ -650,14 +636,12 @@ ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boo // toggle: Set overlay status of selected document ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return RichTextMenu.Instance?.fontSize.replace('px', ''); } if (typeof size === 'number') size = size.toString(); if (size && Number(size).toString() === size) size += 'px'; - if (editorView) RichTextMenu.Instance.setFontSize(size); - else Doc.UserDoc()._fontSize = size; + RichTextMenu.Instance.setFontSize(size); }); ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 81ac45521..314696251 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1387,10 +1387,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent mark.type === schema.marks.user_mark)) { + if (this._editorView) { this._editorView.state.storedMarks = [ ...(this._editorView.state.storedMarks ?? []), - schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), + ...(!this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark) ? [schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })] : []), ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 0cbe60c0c..42a204e1d 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -64,6 +64,7 @@ export class RichTextMenu extends AntimodeMenu { super(props); runInAction(() => { RichTextMenu.Instance = this; + this.updateMenu(undefined, undefined, props); this._canFade = false; this.Pinned = true; }); @@ -103,13 +104,12 @@ export class RichTextMenu extends AntimodeMenu { return; } this.view = view; - if (!view || !view.hasFocus()) { - return; - } props && (this.editorProps = props); // Don't do anything if the document/selection didn't change - if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return; + if (view && view.hasFocus()) { + if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return; + } // update active marks const activeMarks = this.getActiveMarksOnSelection(); @@ -124,9 +124,9 @@ export class RichTextMenu extends AntimodeMenu { this.activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); - this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; - this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; - this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...'; + this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; + this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; // update link in current selection @@ -279,28 +279,15 @@ export class RichTextMenu extends AntimodeMenu { this._superscriptActive = false; activeMarks.forEach(mark => { + // prettier-ignore switch (mark.name) { - case 'noAutoLinkAnchor': - this._noLinkActive = true; - break; - case 'strong': - this._boldActive = true; - break; - case 'em': - this._italicsActive = true; - break; - case 'underline': - this._underlineActive = true; - break; - case 'strikethrough': - this._strikethroughActive = true; - break; - case 'subscript': - this._subscriptActive = true; - break; - case 'superscript': - this._superscriptActive = true; - break; + case 'noAutoLinkAnchor': this._noLinkActive = true; break; + case 'strong': this._boldActive = true; break; + case 'em': this._italicsActive = true; break; + case 'underline': this._underlineActive = true; break; + case 'strikethrough': this._strikethroughActive = true; break; + case 'subscript': this._subscriptActive = true; break; + case 'superscript': this._superscriptActive = true; break; } }); } @@ -342,14 +329,13 @@ export class RichTextMenu extends AntimodeMenu { if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { this.TextView.dataDoc.fontSize = fontSize; this.view.focus(); - this.updateMenu(this.view, undefined, this.props); } else { const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize }); this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); this.view.focus(); - this.updateMenu(this.view, undefined, this.props); } - } + } else Doc.UserDoc()._fontSize = fontSize; + this.updateMenu(this.view, undefined, this.props); }; setFontFamily = (family: string) => { @@ -357,8 +343,8 @@ export class RichTextMenu extends AntimodeMenu { const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family }); this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); this.view.focus(); - this.updateMenu(this.view, undefined, this.props); - } + } else Doc.UserDoc()._fontFamily = family; + this.updateMenu(this.view, undefined, this.props); }; setHighlight(color: String, view: EditorView, dispatch: any) { @@ -368,13 +354,13 @@ export class RichTextMenu extends AntimodeMenu { this.setMark(highlightMark, view.state, dispatch, false); } - setColor(color: String, view: EditorView, dispatch: any) { + setColor(color: string) { if (this.view) { - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color }); + const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color }); this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true); - view.focus(); - this.updateMenu(this.view, undefined, this.props); - } + this.view.focus(); + } else Doc.UserDoc().fontColor = color; + this.updateMenu(this.view, undefined, this.props); } // TODO: remove doesn't work -- cgit v1.2.3-70-g09d2 From 7696d85b7b737a29cab189f4c65f395c5de132c7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 13 Sep 2022 10:11:06 -0400 Subject: fixed problem with sharing presentations where preselementbox template couldn't be expanded. --- src/client/documents/Documents.ts | 5 +++-- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/nodes/trails/PresBox.tsx | 21 +++------------------ src/client/views/nodes/trails/PresElementBox.tsx | 6 ++++-- 4 files changed, 11 insertions(+), 23 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ed4c99d70..3bf4bfb03 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -601,6 +601,7 @@ export namespace Docs { DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: defaultDataKey }, + options: { title: 'pres element template', _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' }, }, ], [ @@ -1079,8 +1080,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.FILTER), undefined, { ...(options || {}) }); } - export function PresElementBoxDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); + export function PresElementBoxDocument() { + return Prototypes.get(DocumentType.PRESELEMENT); } export function DataVizDocument(url: string, options?: DocumentOptions) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index aaad1392f..9a1fa8f26 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -115,7 +115,7 @@ export class CurrentUserUtils { /// Initializes collection of templates for notes and click functions static setupDocTemplates(doc: Doc, field="myTemplates") { - DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"}); + DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(), { }); const templates = [ DocCast(doc.presElement), CurrentUserUtils.setupNoteTemplates(doc), diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index c95ece3da..4ac0bdd02 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -3099,12 +3099,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)}>
@@ -3130,12 +3120,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
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 b55fefa87179b9f15fb72d088f527771e90b9bb8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 13 Sep 2022 15:04:06 -0400 Subject: made things consistent by renaming emptyPresentation to emptyTrail and then always making copies of emptyTrail instead of calling the Docs.create constructor --- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 4 ++-- src/client/views/MainView.tsx | 3 ++- src/client/views/collections/TabDocView.tsx | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 75024d9c9..63080f8cf 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1564,7 +1564,7 @@ export namespace DocUtils { const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) .filter(btnDoc => !btnDoc.hidden) .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) - .filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation) + .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail) .map((dragDoc, i) => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), event: undoBatch((args: { x: number; y: number }) => { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7419750b1..3f3816a8a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -207,7 +207,7 @@ export class CurrentUserUtils { DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates); } - /// initalizes the set of "empty" versions of each document type with default fields. e.g.,. emptyNote, emptyPresentation + /// initalizes the set of "empty" versions of each document type with default fields. e.g.,. emptyNote, emptyTrail static creatorBtnDescriptors(doc: Doc): { title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc, backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, @@ -269,7 +269,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, treeViewHideTitle: true, _chromeHidden: true, boxShadow: "0 0" }}, + {key: "Trail", 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, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index bf52e2af0..a96a04ec9 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -523,7 +523,8 @@ export class MainView extends React.Component { @action createNewPresentation = () => { - const pres = Docs.Create.PresDocument({ title: 'Untitled Trail', _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: 'alias', _chromeHidden: true, boxShadow: '0 0' }); + const pres = Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); + Docs.Create.PresDocument({ title: 'Untitled Trail', _viewType: CollectionViewType.Stacking, _fitWidth: true, treeViewHideTitle: true, _width: 400, _height: 500, targetDropAction: 'alias', _chromeHidden: true, boxShadow: '0 0' }); CollectionDockingView.AddSplit(pres, 'left'); Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', pres); // Doc.MyTrails should be created in createDashboard Doc.ActivePresentation = pres; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 042d39285..46386fa71 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -214,7 +214,7 @@ export class TabDocView extends React.Component { const docList = docs instanceof Doc ? [docs] : docs; const batch = UndoManager.StartBatch('pinning doc'); - const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true); + const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); if (!Doc.ActivePresentation) { Doc.AddDocToList(Doc.MyTrails, 'data', curPres); -- cgit v1.2.3-70-g09d2 From 2fb2b57759f6d2c48fc0e7c89b4a3b25694a245c Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 14 Sep 2022 11:34:19 -0400 Subject: made dashboard button hidden since we have dashboard view now. put viewType 'docking' on proto so that all aliases will get it when nesting docking views. --- src/client/documents/Documents.ts | 3 ++- src/client/util/CurrentUserUtils.ts | 4 ++-- src/client/views/nodes/DocumentView.tsx | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 63080f8cf..14e6fe5bb 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -169,6 +169,7 @@ export class DocumentOptions { _raiseWhenDragged?: boolean; // whether a document is brought to front when dragged. _hideContextMenu?: boolean; // whether the context menu can be shown _viewType?: string; // sub type of a collection + viewType?: string; // sub type of a collection _gridGap?: number; // gap between items in masonry view _viewScale?: number; // how much a freeform view has been scaled (zoomed) _overflow?: string; // set overflow behavior @@ -1142,7 +1143,7 @@ export namespace Docs { } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: 'remove|add', ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: 'remove|add', ...options, viewType: CollectionViewType.Docking, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } export function DirectoryImportDocument(options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 493bbab54..0587cf076 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -326,13 +326,13 @@ export class CurrentUserUtils { const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; const getActiveDashTrails = "Doc.ActiveDashboard?.myTrails"; return [ - { title: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", }, + { title: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", funcs: {hidden:"true"} }, { title: "Search", target: this.setupSearcher(doc, "mySearcher"), icon: "search", }, { title: "Files", target: this.setupFilesystem(doc, "myFilesystem"), icon: "folder-open", }, { title: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} }, { title: "Imports", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", }, { title: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", }, - { title: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs:{badgeValue:badgeValue}}, + { title: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs: {badgeValue:badgeValue}}, { title: "Trails", target: Doc.UserDoc(), icon: "pres-trail", funcs: {target: getActiveDashTrails}}, { title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} }, ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}})); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e628c2e44..cab0e744f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1000,7 +1000,7 @@ export class DocumentViewInternal extends DocComponent Date: Tue, 20 Sep 2022 11:52:44 -0400 Subject: display error instead of infinitely loading after refresh --- src/client/documents/Documents.ts | 4 +++- src/client/views/collections/CollectionSubView.tsx | 2 ++ src/client/views/nodes/LoadingBox.tsx | 9 +++++++++ src/fields/Doc.ts | 22 ++++++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 14e6fe5bb..4f652b6e4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1758,7 +1758,7 @@ export namespace DocUtils { const full = { ...options, _width: 400, title: name }; const pathname = Utils.prepend(result.accessPaths.agnostic.client); const doc = await DocUtils.DocumentFromType(type, pathname, full, rootDoc); - rootDoc && (rootDoc.isLoading = undefined); + rootDoc && (rootDoc.isLoading = undefined) && Doc.removeCurrentlyLoading(rootDoc); if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; @@ -1840,6 +1840,7 @@ export namespace DocUtils { if (overwriteDoc) { overwriteDoc.isLoading = false; overwriteDoc.errorMessage = (result as any).message; + Doc.removeCurrentlyLoading(overwriteDoc); } } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); }); @@ -1868,6 +1869,7 @@ export namespace DocUtils { if (overwriteDoc) { overwriteDoc.isLoading = false; overwriteDoc.errorMessage = (result as any).message; + Doc.removeCurrentlyLoading(overwriteDoc); } } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); }); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7fc1a800b..3ae965af0 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -465,12 +465,14 @@ export function CollectionSubView(moreProps?: X) { const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); loading.isLoading = true; + Doc.addCurrentlyLoading(loading); DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { generatedDocuments.push( ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); loading.isLoading = true; + Doc.addCurrentlyLoading(loading); DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index acf728ebb..220cd0880 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -1,6 +1,8 @@ +import { observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import ReactLoading from 'react-loading'; +import { Doc } from '../../../fields/Doc'; import { StrCast } from '../../../fields/Types'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; @@ -33,6 +35,13 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { return FieldView.LayoutString(LoadingBox, fieldKey); } + componentDidMount() { + if (!Doc.CurrentlyLoading || !Doc.CurrentlyLoading.includes(this.rootDoc)) { + this.rootDoc.isLoading = false; + this.rootDoc.errorMessage = 'Upload was interrupted, please try again'; + } + } + render() { return (
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index cf505c45b..74a3d8cf2 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -149,6 +149,28 @@ export class Doc extends RefField { public static set MainDocId(id: string | undefined) { this.mainDocId = id; } + + @observable public static CurrentlyLoading: Doc[]; + // removes from currently loading display + @action + public static removeCurrentlyLoading(doc: Doc) { + if (Doc.CurrentlyLoading) { + const index = Doc.CurrentlyLoading.indexOf(doc); + index !== -1 && Doc.CurrentlyLoading.splice(index, 1); + } + } + + // adds doc to currently loading display + @action + public static addCurrentlyLoading(doc: Doc) { + if (!Doc.CurrentlyLoading) { + Doc.CurrentlyLoading = []; + } + if (Doc.CurrentlyLoading.indexOf(doc) === -1) { + Doc.CurrentlyLoading.push(doc); + } + } + @observable public static GuestDashboard: Doc | undefined; @observable public static GuestTarget: Doc | undefined; @observable public static GuestMobile: Doc | undefined; -- cgit v1.2.3-70-g09d2 From 23099cff00b2bc3343cc7e83c4920d465f08b985 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 21 Sep 2022 20:42:04 -0400 Subject: added error message for file upload errors before parsing. --- src/client/documents/Documents.ts | 2 +- src/server/ApiManagers/UploadManager.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4f652b6e4..029c3033d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1864,7 +1864,7 @@ export namespace DocUtils { const { source: { name, type }, result, - } = upfiles.lastElement(); + } = upfiles.lastElement() ?? { source: { name: '', type: '' }, result: { message: 'upload failed' } }; if ((result as any).message) { if (overwriteDoc) { overwriteDoc.isLoading = false; diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 0b6e18743..5077bced2 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -60,6 +60,18 @@ export default class UploadManager extends ApiManager { return new Promise(resolve => { form.parse(req, async (_err, _fields, files) => { const results: Upload.FileResponse[] = []; + if (_err.message) { + results.push({ + source: { + size: 0, + path: 'none', + name: 'none', + type: 'none', + toJSON: () => ({ name: 'none', path: '' }), + }, + result: { name: 'failed upload', message: `${_err.message}` }, + }); + } for (const key in files) { const f = files[key]; if (!Array.isArray(f)) { -- cgit v1.2.3-70-g09d2 From 48e0885b405cbfe728b1eda78efc19e15a104426 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 22 Sep 2022 16:00:17 -0400 Subject: fixed error messages on loading to not generate temporary error message when there is no error. --- src/client/documents/Documents.ts | 22 ++++++---------------- src/client/views/collections/CollectionSubView.tsx | 2 -- src/client/views/nodes/LoadingBox.tsx | 12 +++++------- 3 files changed, 11 insertions(+), 25 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 029c3033d..f046af684 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -908,7 +908,7 @@ export namespace Docs { export function ColorDocument(options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - export function LoadingDocument(file: File | string, options: DocumentOptions, ytString?: string) { + export function LoadingDocument(file: File | string, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, ''); } @@ -1758,7 +1758,6 @@ export namespace DocUtils { const full = { ...options, _width: 400, title: name }; const pathname = Utils.prepend(result.accessPaths.agnostic.client); const doc = await DocUtils.DocumentFromType(type, pathname, full, rootDoc); - rootDoc && (rootDoc.isLoading = undefined) && Doc.removeCurrentlyLoading(rootDoc); if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; @@ -1790,6 +1789,9 @@ export namespace DocUtils { if (Upload.isVideoInformation(result)) { proto['data-duration'] = result.duration; } + if (rootDoc) { + Doc.removeCurrentlyLoading(rootDoc); + } generatedDocuments.push(doc); } } @@ -1818,17 +1820,6 @@ export namespace DocUtils { return tbox; } - export async function uploadYoutubeVideo(videoId: string, options: DocumentOptions) { - const generatedDocuments: Doc[] = []; - for (const { - source: { name, type }, - result, - } of await Networking.UploadYoutubeToServer(videoId)) { - name && processFileupload(generatedDocuments, name, type, result, options); - } - return generatedDocuments; - } - export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) { const generatedDocuments: Doc[] = []; Networking.UploadYoutubeToServer(videoId).then(upfiles => { @@ -1839,7 +1830,7 @@ export namespace DocUtils { if ((result as any).message) { if (overwriteDoc) { overwriteDoc.isLoading = false; - overwriteDoc.errorMessage = (result as any).message; + overwriteDoc.loadingError = (result as any).message; Doc.removeCurrentlyLoading(overwriteDoc); } } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); @@ -1867,8 +1858,7 @@ export namespace DocUtils { } = upfiles.lastElement() ?? { source: { name: '', type: '' }, result: { message: 'upload failed' } }; if ((result as any).message) { if (overwriteDoc) { - overwriteDoc.isLoading = false; - overwriteDoc.errorMessage = (result as any).message; + overwriteDoc.loadingError = (result as any).message; Doc.removeCurrentlyLoading(overwriteDoc); } } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 3ae965af0..30759b766 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -464,14 +464,12 @@ export function CollectionSubView(moreProps?: X) { if (typeof files === 'string') { const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); - loading.isLoading = true; Doc.addCurrentlyLoading(loading); DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { generatedDocuments.push( ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); - loading.isLoading = true; Doc.addCurrentlyLoading(loading); DocUtils.uploadFileToDoc(file, {}, loading); return loading; diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 220cd0880..53390328f 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -1,4 +1,3 @@ -import { observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import ReactLoading from 'react-loading'; @@ -36,19 +35,18 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { } componentDidMount() { - if (!Doc.CurrentlyLoading || !Doc.CurrentlyLoading.includes(this.rootDoc)) { - this.rootDoc.isLoading = false; - this.rootDoc.errorMessage = 'Upload was interrupted, please try again'; + if (!Doc.CurrentlyLoading?.includes(this.rootDoc)) { + this.rootDoc.loadingError = 'Upload interrupted, please try again'; } } render() { return ( -
+
-

{this.rootDoc.isLoading ? 'Loading (can take several minutes):' : StrCast(this.rootDoc.errorMessage, 'Error Loading File:')}

+

{StrCast(this.rootDoc.loadingError, 'Loading (can take several minutes):')}

{StrCast(this.rootDoc.title)} - {!this.rootDoc.isLoading ? null : } + {this.rootDoc.loadingError ? null : }
); -- cgit v1.2.3-70-g09d2 From 4580a4193edb451f628eee8be2d3c31d06b1a3a7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 27 Sep 2022 14:56:28 -0400 Subject: fixed so that following link to video doesn't zoom the video (works the same way as PDFs or web pages -- if the document is 'scrollable', then don't zoom in on it as well). --- src/client/documents/Documents.ts | 2 +- src/client/util/DocumentManager.ts | 4 +- .../views/collections/CollectionNoteTakingView.tsx | 2 +- .../collections/CollectionStackedTimeline.tsx | 6 +- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +-- src/client/views/nodes/DocumentView.tsx | 83 +++++++++++----------- 8 files changed, 55 insertions(+), 54 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f046af684..fb68fba38 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1307,7 +1307,7 @@ export namespace DocUtils { }); } - export function DefaultFocus(doc: Doc, options?: DocFocusOptions) { + export function DefaultFocus(doc: Doc, options: DocFocusOptions) { options?.afterFocus?.(false); } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 3a6c43773..50190061a 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -228,7 +228,7 @@ export class DocumentManager { ); return; } else if (!docView && targetDoc.type !== DocumentType.MARKER) { - annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below + annoContainerView.focus(targetDoc, {}); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below } } if (focusView) { @@ -337,7 +337,7 @@ export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { const showDoc = context || doc; const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc; - CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc)); + CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc, {})); } } ScriptingGlobals.add(DocFocusOrOpen); diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 92c0bc341..b0f64ed60 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -193,7 +193,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { }; // let's dive in and get the actual document we want to drag/move around - focusDocument = (doc: Doc, options?: DocFocusOptions) => { + 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]); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index c694e17fb..7bf798656 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -23,7 +23,7 @@ import { AudioWaveform } from '../AudioWaveform'; import { CollectionSubView } from '../collections/CollectionSubView'; import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; -import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps, DocumentViewSharedProps } from '../nodes/DocumentView'; +import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, DocumentViewSharedProps } from '../nodes/DocumentView'; import { LabelBox } from '../nodes/LabelBox'; import './CollectionStackedTimeline.scss'; import { VideoBox } from '../nodes/VideoBox'; @@ -828,9 +828,9 @@ class StackedTimelineAnchor extends React.Component // renders anchor LabelBox renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as any }); - const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => { + const focusFunc = (doc: Doc, options: DocFocusOptions) => { this.props.playLink(mark); - this.props.focus(doc, { willZoom, scale, afterFocus, docTransform }); + this.props.focus(doc, options); }; return { anchor, diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index cc006c734..ba29b1d6f 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -251,7 +251,7 @@ export class CollectionStackingView extends CollectionSubView { + focusDocument = (doc: Doc, options: DocFocusOptions) => { Doc.BrushDoc(doc); let focusSpeed = 0; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 35edbe684..b1f57f3fd 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -367,7 +367,7 @@ export class TabDocView extends React.Component { return NumCast(Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex].presentationTargetDoc, Doc, null)._currentFrame); }; @action - focusFunc = (doc: Doc, options?: DocFocusOptions) => { + focusFunc = (doc: Doc, options: DocFocusOptions) => { const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap; if (options?.willZoom !== false && shrinkwrap && this._document) { const focusSpeed = NumCast(this._document.focusSpeed, 500); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e9d0826cd..7b6944e1e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1112,7 +1112,7 @@ export class CollectionFreeFormView extends CollectionSubView(res => setTimeout(() => res(runInAction(() => (this._viewTransition = 0))), this._viewTransition)); // set transition to be smooth, then reset } - focusDocument = (doc: Doc, options?: DocFocusOptions) => { + focusDocument = (doc: Doc, options: DocFocusOptions) => { const state = HistoryUtil.getState(); // TODO This technically isn't correct if type !== "doc", as @@ -1131,13 +1131,13 @@ export class CollectionFreeFormView extends CollectionSubView new Promise(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f18fd8024..ba061ce8f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -80,7 +80,7 @@ export interface DocFocusOptions { instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom) } export type DocAfterFocusFunc = (notFocused: boolean) => Promise; -export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void; +export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => void; export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { updateIcon?: () => void; // updates the icon representation of the document @@ -534,7 +534,7 @@ export class DocumentViewInternal extends DocComponent { + focus = (anchor: Doc, options: DocFocusOptions) => { LightboxView.SetCookie(StrCast(anchor['cookies-set'])); // copying over VIEW fields immediately allows the view type to switch to create the right _componentView Array.from(Object.keys(Doc.GetProto(anchor))) @@ -1259,46 +1259,47 @@ export class DocumentViewInternal extends DocComponent users.user.email === this.dataDoc.author)?.sharingDoc.userColor, Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : 'rgba(0,0,0,0.4)' ); - const titleView = !showTitle || Doc.noviceMode ? null : ( -
- field.trim()) - .map(field => targetDoc[field]?.toString()) - .join('\\')} - display={'block'} - fontSize={10} - GetValue={() => (showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle)} - SetValue={undoBatch((input: string) => { - if (input?.startsWith('#')) { - if (this.props.showTitle) { - this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined; + const titleView = + !showTitle || Doc.noviceMode ? null : ( +
+ field.trim()) + .map(field => targetDoc[field]?.toString()) + .join('\\')} + display={'block'} + fontSize={10} + GetValue={() => (showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle)} + SetValue={undoBatch((input: string) => { + if (input?.startsWith('#')) { + if (this.props.showTitle) { + this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined; + } else { + Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : 'creationDate'; + } } else { - Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : 'creationDate'; + var value = input.replace(new RegExp(showTitle + '='), '') as string | number; + if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value); + if (showTitle.includes('Date') || showTitle === 'author') return true; + Doc.SetInPlace(targetDoc, showTitle, value, true); } - } else { - var value = input.replace(new RegExp(showTitle + '='), '') as string | number; - if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value); - if (showTitle.includes('Date') || showTitle === 'author') return true; - Doc.SetInPlace(targetDoc, showTitle, value, true); - } - return true; - })} - /> -
- ); + return true; + })} + /> +
+ ); return this.props.hideTitle || (!showTitle && !showCaption) ? ( this.contents ) : ( @@ -1557,7 +1558,7 @@ export class DocumentView extends React.Component { } 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); + 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())) { return undefined; -- cgit v1.2.3-70-g09d2 From 2de2e154912d95a15e5f890c9d1a3a7a11610107 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 29 Sep 2022 20:44:33 -0400 Subject: a bunch of changes to trails: link to trail to initiate trail when following link. cleanly separate pin layout data vs. content data. --- src/client/documents/Documents.ts | 3 + src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DocumentManager.ts | 2 +- src/client/util/type_decls.d | 3 + src/client/views/DocumentButtonBar.scss | 22 ++- src/client/views/DocumentButtonBar.tsx | 71 +++++++--- src/client/views/DocumentDecorations.tsx | 3 +- src/client/views/MainView.tsx | 1 + src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 42 +++--- .../collections/collectionFreeForm/MarqueeView.tsx | 12 +- .../collectionLinear/CollectionLinearView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +- src/client/views/nodes/ScriptingBox.tsx | 5 + src/client/views/nodes/trails/PresBox.tsx | 147 +++++++++------------ src/client/views/nodes/trails/PresElementBox.tsx | 66 ++++++--- 18 files changed, 225 insertions(+), 170 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fb68fba38..4ca9972f8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1398,6 +1398,9 @@ export namespace DocUtils { heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name, + altKey: 'boolean', + ctrlKey: 'boolean', + shiftKey: 'boolean', }); } }); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fdac3c00a..6f9975be4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -657,7 +657,7 @@ 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: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, { 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_)'}}, diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 50190061a..9a46d20de 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -244,7 +244,7 @@ export class DocumentManager { res(ViewAdjustment.doNothing); }), }); - if (focusView.props.Document.layoutKey === 'layout_icon') { + if (focusView.props.Document.layoutKey === 'layout_icon' && focusView.rootDoc.type !== DocumentType.SCRIPTING) { focusView.iconify(() => doFocus(true)); } else { doFocus(false); diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 9063dc894..1a93bbe59 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -67,6 +67,9 @@ interface RegExp { readonly sticky: boolean; readonly unicode: boolean; } +interface Date { + now() : string; +} interface String { codePointAt(pos: number): number | undefined; includes(searchString: string, position?: number): boolean; diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index a112f4745..1e93ba5e2 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -1,6 +1,6 @@ -@import "global/globalCssVariables"; +@import 'global/globalCssVariables'; -$linkGap : 3px; +$linkGap: 3px; .documentButtonBar-linkFlyout { grid-column: 2/4; @@ -18,6 +18,21 @@ $linkGap : 3px; cursor: pointer; } +.documentButtonBar-pinTypes { + position: absolute; + display: flex; + width: 60px; + top: -14px; + background: black; + height: 20px; + align-items: center; +} +.documentButtonBar-pinIcon { + &:hover { + background-color: lightblue; + } +} + .documentButtonBar-linkButton-empty, .documentButtonBar-linkButton-nonempty { height: 20px; @@ -99,7 +114,6 @@ $linkGap : 3px; transform: scale(1.05); } - @-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); @@ -127,4 +141,4 @@ $linkGap : 3px; 100% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); } -} \ No newline at end of file +} diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 0bfe6e87a..d42ff436f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,8 +5,8 @@ import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, DocCast, NumCast } from '../../fields/Types'; -import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; +import { Cast, NumCast } from '../../fields/Types'; +import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs } from '../documents/Documents'; @@ -26,7 +26,6 @@ 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; @@ -159,12 +158,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV })(); return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? null : ( - -
{title}
- - }> + {title}
}>
(DocumentV size="sm" style={{ WebkitAnimation: animation, MozAnimation: animation }} icon={(() => { + // prettier-ignore switch (this.openHover) { default: - case UtilityButtonState.Default: - return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; - case UtilityButtonState.OpenRight: - return 'arrow-alt-circle-right'; - case UtilityButtonState.OpenExternally: - return 'share'; + case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; + case UtilityButtonState.OpenRight: return 'arrow-alt-circle-right'; + case UtilityButtonState.OpenExternally: return 'share'; } })()} /> @@ -231,21 +223,66 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); } + @observable expandPin = false; + @observable subPin = ''; @computed get pinButton() { const targetDoc = this.view0?.props.Document; + const pinBtn = (pinDocLayout: boolean, pinDocContent: boolean, icon: IconProp) => { + const tooltip = `Pin Document and Save ${this.subPin} to trail`; + return !tooltip ? null : ( + {tooltip}
}> +
+ + (this.subPin = + (pinDocLayout ? 'Layout' : '') + + (pinDocLayout && pinDocContent ? ' &' : '') + + (pinDocContent ? ' Content View' : '') + + (pinDocLayout && pinDocContent ? '(shift+alt)' : pinDocLayout ? '(shift)' : pinDocContent ? '(alt)' : '')) + )} + onPointerLeave={action(e => (this.subPin = ''))} + onClick={e => { + const docs = this.props + .views() + .filter(v => v) + .map(dv => dv!.rootDoc); + TabDocView.PinDoc(docs, { pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + e.stopPropagation(); + }} + /> +
+ + ); + }; return !targetDoc ? null : ( - {SelectionManager.Views().length > 1 ? 'Pin multiple documents to trail (use shift to pin with view)' : 'Pin to trail (use shift to pin with view)'}
}> + {`Pin Document ${SelectionManager.Views().length > 1 ? 'multiple documents' : ''} to Trail`}
}>
(this.expandPin = true))} + onPointerLeave={action(e => (this.expandPin = false))} onClick={e => { const docs = this.props .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocView: e.shiftKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + e.stopPropagation(); }}> + {this.expandPin ? ( +
+ {pinBtn(true, false, 'window-maximize')} + {pinBtn(false, true, 'address-card')} + {pinBtn(true, true, 'id-card')} +
+ ) : null}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4675571c2..3e0de6d15 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -678,8 +678,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth)); bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); - // Rotation constants: Only allow rotation on ink and images - const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox || seldoc.ComponentView instanceof VideoBox; + const useRotation = true; // when do we want an object to not rotate? const rotation = NumCast(seldoc.rootDoc._jitterRotation); const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 857a5522c..350883f70 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -303,6 +303,7 @@ export class MainView extends React.Component { fa.faStop, fa.faCalculator, fa.faWindowMaximize, + fa.faIdCard, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft, diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index b1f57f3fd..c4da70371 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -233,7 +233,7 @@ export class TabDocView extends React.Component { pinDoc.presentationTargetDoc = doc; pinDoc.title = doc.title + ' - Slide'; pinDoc.data = new List(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data - pinDoc.presMovement = pinProps?.pinDocView && !pinProps?.pinWithView ? PresMovement.None : PresMovement.Zoom; + pinDoc.presMovement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; pinDoc.groupWithUp = false; pinDoc.context = curPres; // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index c34a6faaa..640984ad6 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -782,7 +782,7 @@ export class TreeView extends React.Component { onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); - refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); + refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {}); ignoreEvent = (e: any) => { if (this.props.isContentActive(true)) { e.stopPropagation(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7b6944e1e..8824c2b01 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1719,7 +1719,7 @@ export class CollectionFreeFormView extends CollectionSubView (this.Document._fitContentsToBox = !this.fitContentsToBox), icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt', }); - appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinDocView: true, panelWidth: this.props.PanelWidth(), panelHeight: this.props.PanelHeight() }), icon: 'map-pin' }); + appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); @@ -2249,6 +2249,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() })); +ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) { + !readOnly && SelectionManager.Views().forEach(view => TabDocView.PinDoc(view.rootDoc, { pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })); }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 8a8b528f6..488f51d77 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -1,11 +1,11 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { observer } from "mobx-react"; -import { unimplementedFunction } from "../../../../Utils"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { observer } from 'mobx-react'; +import { unimplementedFunction } from '../../../../Utils'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; @observer export class MarqueeOptionsMenu extends AntimodeMenu { @@ -25,44 +25,34 @@ export class MarqueeOptionsMenu extends AntimodeMenu { } render() { - const presPinWithViewIcon = ; + const presPinWithViewIcon = ; const buttons = [ Create a Collection
} placement="bottom"> - , Create a Grouping
} placement="bottom"> - , Summarize Documents
} placement="bottom"> - , Delete Documents
} placement="bottom"> - , - Pin with selected region
} placement="bottom"> -
} placement="bottom"> + , ]; return this.getElement(buttons); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 584c9690f..a020b67cd 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -18,7 +18,7 @@ import { Transform } from '../../../util/Transform'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; -import { PinViewProps, PresBox } from '../../nodes/trails/PresBox'; +import { PresBox } from '../../nodes/trails/PresBox'; import { VideoBox } from '../../nodes/VideoBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; @@ -61,6 +61,11 @@ export interface MarqueeViewBounds { @observer export class MarqueeView extends React.Component { + public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) { + const ps = NumCast(pinDoc._viewScale, 1); + return { left: NumCast(pinDoc._panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps }; + } + private _commandExecuted = false; @observable _lastX: number = 0; @observable _lastY: number = 0; @@ -426,10 +431,7 @@ export class MarqueeView extends React.Component { const doc = this.props.Document; - const viewOptions: PinViewProps = { - bounds: this.Bounds, - }; - TabDocView.PinDoc(doc, { pinWithView: viewOptions, pinDocView: true }); + TabDocView.PinDoc(doc, { pinViewport: this.Bounds }); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); }; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index f6eb2fce4..92f6bbb64 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -208,7 +208,7 @@ export class CollectionLinearView extends CollectionSubView() { checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle} onChange={action(e => { - ScriptCast(this.Document.onClick).script.run({ + ScriptCast(this.Document.onClick)?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ba061ce8f..913d5a7ef 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -577,6 +577,7 @@ export class DocumentViewInternal extends DocComponent this.onClickHandler.script.run( { @@ -602,6 +603,7 @@ export class DocumentViewInternal extends DocComponent { @@ -480,6 +481,10 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent { + return undefined; + }; + getSuggestedParams(pos: number) { const firstScript = this.rawText.slice(0, pos); const indexP = firstScript.lastIndexOf('.'); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0c4d514cd..18441aace 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -31,19 +31,16 @@ import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentVie import { FieldView, FieldViewProps } from '../FieldView'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +import { privateEncrypt } from 'crypto'; +import { ScriptingBox } from '../ScriptingBox'; export interface PinProps { audioRange?: boolean; activeFrame?: number; hidePresBox?: boolean; - pinWithView?: PinViewProps; - pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document - panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) - panelHeight?: number; -} - -export interface PinViewProps { - bounds: MarqueeViewBounds; + pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) + pinDocLayout?: boolean; // pin layout info (width/height/x/y) + pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text) } @observer @@ -268,6 +265,7 @@ export class PresBox extends ViewBoxBaseComponent() { // Case 3: Last slide and presLoop is toggled ON or it is in Edit mode this.nextSlide(0); } + return this.itemIndex; }; // Called when the user activates 'back' - to move to the previous part of the pres. trail @@ -298,6 +296,7 @@ export class PresBox extends ViewBoxBaseComponent() { // Case 3: Pres loop is on so it should go to the last slide this.gotoDocument(this.childDocs.length - 1, activeItem); } + return this.itemIndex; }; //The function that is called when a document is clicked or reached through next or back. @@ -351,7 +350,7 @@ 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); - const dataview = [DocumentType.INK].includes(target.type as any) && target.activeFrame === undefined; + const dataview = [DocumentType.INK, DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined; const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; return { scrollable, pannable, temporal, clippable, dataview, textview }; } @@ -383,7 +382,7 @@ export class PresBox extends ViewBoxBaseComponent() { const dv = DocumentManager.Instance.getDocumentView(bestTarget); if (dv) { const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height); - activeItem.presMovement === 'zoom' && (bestTarget._viewScale = activeItem.presZoom !== undefined ? computedScale : Math.min(computedScale, NumCast(bestTarget._viewScale))); + activeItem.presMovement === 'zoom' && (bestTarget._viewScale = computedScale); dv.ComponentView?.brushView?.(viewport); } } else { @@ -400,47 +399,40 @@ export class PresBox extends ViewBoxBaseComponent() { /// target doc when navigating to it. @action 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; - pinDoc.presPinView = true; - pinDoc.presPinViewX = bounds.left + bounds.width / 2; - pinDoc.presPinViewY = bounds.top + bounds.height / 2; - 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); - pinDoc.presPinView = (pinProps?.pinWithView ? true : false) || scrollable || temporal || pannable || clippable || dataview || textview || pinProps.activeFrame !== undefined; + const { scrollable, pannable, temporal, clippable, dataview, textview } = this.pinDataTypes(pinDoc); + if (pinProps?.pinDocLayout) { + pinDoc.presPinLayout = true; pinDoc.presX = NumCast(targetDoc.x); pinDoc.presY = NumCast(targetDoc.y); pinDoc.presRot = NumCast(targetDoc.jitterRotation); pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); - - if (scrollable) { - pinDoc.presPinViewScroll = pinDoc._scrollTop; - } + } + if (pinProps?.pinDocContent) { + pinDoc.presPinData = scrollable || temporal || pannable || clippable || dataview || textview || pinProps.activeFrame !== undefined; + if (textview) pinDoc.presData = targetDoc.text instanceof ObjectField ? targetDoc.text[Copy]() : targetDoc.text; + if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; + if (dataview) pinDoc.presData = targetDoc.data instanceof ObjectField ? targetDoc.data[Copy]() : targetDoc.data; + if (pannable) { + pinDoc.presPinViewX = NumCast(pinDoc._panX); + pinDoc.presPinViewY = NumCast(pinDoc._panY); + pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + } if (temporal) { pinDoc.presStartTime = pinDoc._currentTimecode; const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(pinDoc.presStartTime) + 0.1); pinDoc.presEndTime = NumCast(pinDoc.clipEnd, duration); } - 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 || scrollable) { - 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, 1); - if (pw && ph && ps) { - pinDoc.presPinViewBounds = new List([panX - pw / 2 / ps, panY - ph / 2 / ps, panX + pw / 2 / ps, panY + ph / 2 / ps]); - } - pinDoc.presPinViewX = panX; - pinDoc.presPinViewY = panY; - pinDoc.presPinViewScale = ps; - } + } + if (pinProps?.pinViewport) { + // If pinWithView option set then update scale and x / y props of slide + const bounds = pinProps.pinViewport; + pinDoc.presPinView = true; + pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + pinDoc.presPinViewX = bounds.left + bounds.width / 2; + pinDoc.presPinViewY = bounds.top + bounds.height / 2; + pinDoc.presPinViewBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); } } @@ -497,7 +489,7 @@ export class PresBox extends ViewBoxBaseComponent() { }; static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) { - if (activeItem.presPinView && DocCast(targetDoc.context)?._currentFrame === undefined) { + if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) { const transTime = NumCast(activeItem.presTransition, 500); const presTransitionTime = `all ${transTime}ms`; targetDoc._dataTransition = presTransitionTime; @@ -514,11 +506,13 @@ export class PresBox extends ViewBoxBaseComponent() { } else if (targetDoc && activeItem.presMovement !== PresMovement.None) { LightboxView.SetLightboxDoc(undefined); const zooming = activeItem.presMovement !== PresMovement.Pan; - DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, finished, undefined, true, NumCast(activeItem.presZoom)); + DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, finished, undefined, true, NumCast(activeItem.presZoom, 1)); + } else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { + (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); } // 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 (activeItem.presPinData || activeItem.presPinView) { clearTimeout(PresBox._navTimer); // targetDoc may or may not be displayed. this gets the first available document (or alias) view that matches targetDoc const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; @@ -615,42 +609,19 @@ export class PresBox extends ViewBoxBaseComponent() { //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc @action startAutoPres = (startSlide: number) => { - this.updateCurrentPresentation(); - let activeItem: Doc = this.activeItem; - let targetDoc: Doc = this.targetDoc; - let duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms))); - const load = async () => { - // Wrap the loop into an async function for this to work - for (var i = startSlide; i < this.childDocs.length; i++) { - activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); - targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - if (duration < 100) { - duration = 2500; - } - if (NumCast(targetDoc.lastFrame) > 0) { - for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) { - await timer(duration / NumCast(targetDoc.lastFrame)); - this.next(); - } - } - - await timer(duration); - this.next(); // then the created Promise can be awaited - if (i === this.childDocs.length - 1) { - setTimeout(() => { - clearTimeout(this._presTimer); - if (this.layoutDoc.presStatus === 'auto' && !this.layoutDoc.presLoop) this.layoutDoc.presStatus = PresStatus.Manual; - else if (this.layoutDoc.presLoop) this.startAutoPres(0); - }, duration); - } - } - }; this.layoutDoc.presStatus = PresStatus.Autoplay; this.startPresentation(startSlide); - this.gotoDocument(startSlide, activeItem); - load(); + clearTimeout(this._presTimer); + const func = (itemIndex: number) => { + if (itemIndex === this.next()) this.layoutDoc.presStatus = PresStatus.Manual; + else + this._presTimer = setTimeout( + () => this.layoutDoc.presStatus !== PresStatus.Manual && func(this.itemIndex), + NumCast(this.activeItem.presDuration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presTransition) + ); + }; + + func(this.itemIndex); }; // The function pauses the auto presentation @@ -659,7 +630,6 @@ export class PresBox extends ViewBoxBaseComponent() { if (this.layoutDoc.presStatus === PresStatus.Autoplay) { if (this._presTimer) clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; - this.layoutDoc.presLoop = false; this.childDocs.forEach(this.stopTempMedia); } }; @@ -667,7 +637,6 @@ export class PresBox extends ViewBoxBaseComponent() { //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. resetPresentation = () => { - this.rootDoc._itemIndex = 0; this.childDocs .map(doc => Cast(doc.presentationTargetDoc, Doc, null)) .filter(doc => doc instanceof Doc) @@ -706,8 +675,7 @@ export class PresBox extends ViewBoxBaseComponent() { * @param startIndex: index that the presentation will start at */ startPresentation = (startIndex: number) => { - this.updateCurrentPresentation(); - this.childDocs.map(doc => { + this.childDocs.forEach(doc => { const tagDoc = doc.presentationTargetDoc as Doc; if (doc.presHideBefore && this.childDocs.indexOf(doc) > startIndex) { tagDoc.opacity = 0; @@ -716,6 +684,7 @@ export class PresBox extends ViewBoxBaseComponent() { tagDoc.opacity = 0; } }); + this.gotoDocument(startIndex, this.activeItem); }; /** @@ -996,7 +965,7 @@ export class PresBox extends ViewBoxBaseComponent() { break; case 'Spacebar': case ' ': - if (this.layoutDoc.presStatus === PresStatus.Manual) this.startAutoPres(this.itemIndex); + if (this.layoutDoc.presStatus === PresStatus.Manual) this.startOrPause(true); else if (this.layoutDoc.presStatus === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer); handled = true; break; @@ -1254,7 +1223,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (activeItem && targetDoc) { const type = targetDoc.type; const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; - const zoom = activeItem.presZoom ? NumCast(activeItem.presZoom) * 100 : 75; + const zoom = NumCast(activeItem.presZoom, 1) * 100; let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); const effect = this.activeItem.presEffect ? this.activeItem.presEffect : 'None'; @@ -1808,6 +1777,11 @@ export class PresBox extends ViewBoxBaseComponent() { ); } + scrollFocus = () => { + this.startOrPause(false); + return undefined; + }; + // Case in which the document has keyframes to navigate to next key frame @action nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => { @@ -2480,7 +2454,7 @@ export class PresBox extends ViewBoxBaseComponent() {
{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
}> -
+
this.startOrPause(true)}>
@@ -2529,7 +2503,8 @@ export class PresBox extends ViewBoxBaseComponent() { } @action - startOrPause = () => { + startOrPause = (makeActive = true) => { + makeActive && this.updateCurrentPresentation(); if (this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startAutoPres(this.itemIndex); else this.pauseAutoPres(); }; @@ -2613,7 +2588,7 @@ export class PresBox extends ViewBoxBaseComponent() {
{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
}> -
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}> +
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), false, false)}>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index e6d08cd53..fe2668492 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -26,6 +26,7 @@ import { PresMovement } from './PresEnums'; import React = require('react'); import { InkField } from '../../../../fields/InkField'; import { RichTextField } from '../../../../fields/RichTextField'; +import { MarqueeView } from '../../collections/collectionFreeForm'; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. @@ -142,7 +143,6 @@ export class PresElementBox extends ViewBoxBaseComponent() { this.presExpandDocumentClick(); }}>
{`${ind + 1}.`}
- {/* style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }} */}
() { */ @undoBatch @action - updateView = (targetDoc: Doc, activeItem: Doc) => { + updateCapturedContainerLayout = (targetDoc: Doc, activeItem: Doc) => { + activeItem.presX = NumCast(targetDoc.x); + activeItem.presY = NumCast(targetDoc.y); + activeItem.presRot = NumCast(targetDoc.jitterRotation); + activeItem.presWidth = NumCast(targetDoc.width); + activeItem.presHeight = NumCast(targetDoc.height); + }; + /** + * Method called for updating the view of the currently selected document + * + * @param targetDoc + * @param activeItem + */ + @undoBatch + @action + updateCapturedViewContents = (targetDoc: Doc, activeItem: Doc) => { switch (targetDoc.type) { case DocumentType.PDF: case DocumentType.WEB: @@ -326,20 +341,22 @@ export class PresElementBox extends ViewBoxBaseComponent() { const clipWidth = targetDoc._clipWidth; activeItem.presPinClipWidth = clipWidth; break; + case DocumentType.COL: default: - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; + const bestView = DocumentManager.Instance.getFirstDocumentView(targetDoc); + if (activeItem.presPinViewBounds && bestView) { + const bounds = MarqueeView.CurViewBounds(targetDoc, bestView.props.PanelWidth(), bestView.props.PanelHeight()); + activeItem.presPinView = true; + activeItem.presPinViewScale = NumCast(targetDoc._viewScale, 1); + activeItem.presPinViewX = bounds.left + bounds.width / 2; + activeItem.presPinViewY = bounds.top + bounds.height / 2; + activeItem.presPinViewBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); + } else { + activeItem.presPinViewX = targetDoc._panX; + activeItem.presPinViewY = targetDoc._panY; + activeItem.presPinViewScale = targetDoc._viewScale; + } } - - activeItem.presX = NumCast(targetDoc.x); - activeItem.presY = NumCast(targetDoc.y); - activeItem.presRot = NumCast(targetDoc.jitterRotation); - activeItem.presWidth = NumCast(targetDoc.width); - activeItem.presHeight = NumCast(targetDoc.height); }; @computed get recordingIsInOverlay() { @@ -506,16 +523,23 @@ export class PresElementBox extends ViewBoxBaseComponent() { {/*
{"Movement speed"}
}>
{this.transition}
*/} {/*
{"Duration"}
}>
{this.duration}
*/}
- Update view
}> -
this.updateView(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinView ? 'flex' : 'none' }}> - V + Update captured doc layout
}> +
this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinLayout ? 'flex' : 'none' }}> + L
- {!Doc.noviceMode && {this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}
}> -
(this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> - e.stopPropagation()} /> + Update captured doc content
}> +
this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinData || activeItem.presPinView ? 'flex' : 'none' }}> + C
- } + + {!Doc.noviceMode && ( + {this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}
}> +
(this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> + e.stopPropagation()} /> +
+ + )} {activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}
}>
Date: Tue, 4 Oct 2022 11:54:55 -0400 Subject: fix to show close and open icons of hideResizers documents. fixes for native size control of text and freeform collections. fixes for lightbox view of native size collections. fix for 'false' script. --- src/client/documents/Documents.ts | 1 + src/client/views/DocumentDecorations.tsx | 24 ++++++++++------------ .../collectionFreeForm/CollectionFreeFormView.tsx | 5 ++++- src/fields/Doc.ts | 6 ++++++ src/fields/ScriptField.ts | 2 +- 5 files changed, 23 insertions(+), 15 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fb68fba38..cd647f5e8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -396,6 +396,7 @@ export namespace Docs { _yMargin: 10, nativeDimModifiable: true, treeViewGrowsHorizontally: true, + nativeHeightUnfrozen: true, forceReflow: true, links: '@links(self)', }, diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4675571c2..d5db45494 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,8 +1,10 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; +import { IconButton } from 'browndash-components'; import { action, computed, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import { FaUndo } from 'react-icons/fa'; import { DateField } from '../../fields/DateField'; import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc'; import { Document } from '../../fields/documentSchemas'; @@ -28,10 +30,8 @@ import { LightboxView } from './LightboxView'; import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; -import React = require('react'); -import { IconButton } from 'browndash-components'; -import { FaUndo } from 'react-icons/fa'; import { VideoBox } from './nodes/VideoBox'; +import React = require('react'); @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -503,7 +503,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } let actualdW = Math.max(width + dW * scale, 20); let actualdH = Math.max(height + dH * scale, 20); - const fixedAspect = nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen); + const fixedAspect = nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable); if (fixedAspect) { if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) { if (dragRight && modifyNativeDim) { @@ -534,7 +534,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } else doc._height = actualdH; } } else { - const maxHeight = 0; //Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); + const maxHeight = doc.nativHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH); dW && (doc._width = actualdW); dH && (doc._autoHeight = false); @@ -722,14 +722,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px', }}> - {hideResizers ? null : ( -
- {hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} - {hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} - {titleArea} - {hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Tab (ctrl: as alias, shift: in new collection)')} -
- )} +
+ {hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} + {hideResizers || hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} + {hideResizers ?
: titleArea} + {hideOpenButton ?
: topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')} +
{hideResizers ? null : ( <>
e.preventDefault()} /> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7b6944e1e..93bf143df 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -156,7 +156,9 @@ export class CollectionFreeFormView extends CollectionSubView this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; shrinkWrap = () => { + if (this.props.DocumentView?.().nativeWidth) return; const vals = this.fitToContentVals; this.layoutDoc._panX = vals.bounds.cx; this.layoutDoc._panY = vals.bounds.cy; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ea3c122a4..0ceaff968 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1402,11 +1402,17 @@ export namespace Doc { layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale; layoutDoc._nativeWidth = undefined; layoutDoc._nativeHeight = undefined; + layoutDoc._forceReflow = undefined; + layoutDoc._nativeHeightUnfrozen = undefined; + layoutDoc._nativeDimModifiable = undefined; } else { layoutDoc._autoHeight = false; if (!Doc.NativeWidth(layoutDoc)) { layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth); layoutDoc._nativeHeight = NumCast(layoutDoc._height, panelHeight); + layoutDoc._forceReflow = true; + layoutDoc._nativeHeightUnfrozen = true; + layoutDoc._nativeDimModifiable = true; } } }); diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 3cb50a4c0..4896c027d 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -104,7 +104,7 @@ export class ScriptField extends ObjectField { } this.rawscript = rawscript; this.setterscript = setterscript; - this.script = script ?? (CompileScript('false') as CompiledScript); + this.script = script ?? (CompileScript('false', { addReturn: true }) as CompiledScript); } // init(callback: (res: Field) => any) { -- cgit v1.2.3-70-g09d2 From 4d21b79bfb87bb5fe81950a432a031f42b99f707 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 6 Oct 2022 10:19:31 -0400 Subject: don't allow golden layout to be created if there's no dockingConfig. --- src/client/documents/Documents.ts | 4 +- .../views/collections/CollectionDockingView.tsx | 57 ++++++++++++---------- 2 files changed, 32 insertions(+), 29 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cd647f5e8..a6a10d91a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1902,8 +1902,8 @@ ScriptingGlobals.add(function makeDelegate(proto: any) { return d; }); ScriptingGlobals.add(function generateLinkTitle(self: Doc) { - const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : ''; - const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : ''; + const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null)?.title : ''; + const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null)?.title : ''; const relation = self.linkRelationship || 'to'; return `${anchor1title} (${relation}) ${anchor2title}`; }); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 40a5876e0..37a2d5e5d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -282,35 +282,38 @@ export class CollectionDockingView extends CollectionSubView() { return true; } setupGoldenLayout = async () => { - const config = StrCast(this.props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.props.Document))); - const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); - const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? []; - - await Promise.all(docids.map(id => DocServer.GetRefField(id))); - - if (this._goldenLayout) { - if (config === JSON.stringify(this._goldenLayout.toConfig())) { - return; - } else { - try { - this._goldenLayout.unbind('tabCreated', this.tabCreated); - this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); - this._goldenLayout.unbind('stackCreated', this.stackCreated); - } catch (e) {} + //const config = StrCast(this.props.Document.dockingConfig, JSON.stringify(DashboardView.resetDashboard(this.props.Document))); + const config = StrCast(this.props.Document.dockingConfig); + if (config) { + const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); + const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) ?? []; + + await Promise.all(docids.map(id => DocServer.GetRefField(id))); + + if (this._goldenLayout) { + if (config === JSON.stringify(this._goldenLayout.toConfig())) { + return; + } else { + try { + this._goldenLayout.unbind('tabCreated', this.tabCreated); + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); + this._goldenLayout.unbind('stackCreated', this.stackCreated); + } catch (e) {} + } + this.tabMap.clear(); + this._goldenLayout.destroy(); } - this.tabMap.clear(); - this._goldenLayout.destroy(); + const glay = (this._goldenLayout = new GoldenLayout(JSON.parse(config))); + glay.on('tabCreated', this.tabCreated); + glay.on('tabDestroyed', this.tabDestroyed); + glay.on('stackCreated', this.stackCreated); + glay.registerComponent('DocumentFrameRenderer', TabDocView); + glay.container = this._containerRef.current; + glay.init(); + glay.root.layoutManager.on('itemDropped', this.tabItemDropped); + glay.root.layoutManager.on('dragStart', this.tabDragStart); + glay.root.layoutManager.on('activeContentItemChanged', this.stateChanged); } - const glay = (this._goldenLayout = new GoldenLayout(JSON.parse(config))); - glay.on('tabCreated', this.tabCreated); - glay.on('tabDestroyed', this.tabDestroyed); - glay.on('stackCreated', this.stackCreated); - glay.registerComponent('DocumentFrameRenderer', TabDocView); - glay.container = this._containerRef.current; - glay.init(); - glay.root.layoutManager.on('itemDropped', this.tabItemDropped); - glay.root.layoutManager.on('dragStart', this.tabDragStart); - glay.root.layoutManager.on('activeContentItemChanged', this.stateChanged); }; componentDidMount: () => void = () => { -- cgit v1.2.3-70-g09d2 From a26f7793c7626da5551774ac9911db1da34affec Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 13 Oct 2022 13:41:27 -0400 Subject: added renaming of dashboards. fixed bug with pin button script. --- src/client/documents/Documents.ts | 4 ++-- src/client/views/DashboardView.tsx | 26 +++++--------------------- 2 files changed, 7 insertions(+), 23 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3ae9a0751..2500eafab 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1408,8 +1408,8 @@ export namespace DocUtils { funcs && Object.keys(funcs).map(key => { const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) { - doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }); + if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) { + doc[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }) : undefined; } }); return doc; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 192e55431..7ebe8d0e3 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -163,18 +163,7 @@ export class DashboardView extends React.Component {
-
this.selectDashboardGroup(DashboardGroup.MyDashboards)}> My Dashboards @@ -187,19 +176,14 @@ export class DashboardView extends React.Component { {this.getDashboards().map(dashboard => { const href = ImageCast(dashboard.thumb)?.url.href; return ( -
{ - this.onContextMenu(dashboard, e); - }} - onClick={e => this.clickDashboard(e, dashboard)}> +
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> + } + />
-
{StrCast(dashboard.title)}
+ e.stopPropagation()} defaultValue={StrCast(dashboard.title)} onChange={e => (Doc.GetProto(dashboard).title = (e.target as any).value)} /> {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ?
unviewed
:
}
Date: Tue, 18 Oct 2022 11:37:27 -0400 Subject: fixed up off-center rotation for ink --- src/client/documents/Documents.ts | 1 + src/client/views/DocumentDecorations.tsx | 44 +++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 15 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2500eafab..2d82ffdf9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -984,6 +984,7 @@ export namespace Docs { I.strokeEndMarker = arrowEnd; I.strokeDash = dash; I.tool = tool; + I.fitWidth = true; I['text-align'] = 'center'; I.title = 'ink'; I.x = options.x as number; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 91fb4e4df..d79cf014d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -310,6 +310,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P e => {} // clickEvent ); }; + + setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => { + const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]); + const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; + const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI); + seldocview.rootDoc.rotateCenterX = final.x / NumCast(seldocview.layoutDoc._width); + seldocview.rootDoc.rotateCenterY = final.y / NumCast(seldocview.layoutDoc._height); + }; + @action onRotateCenterDown = (e: React.PointerEvent): void => { this._isRotating = true; @@ -318,15 +327,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]); - const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; - const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI); - seldocview.rootDoc.rotateCenterX = final.x / NumCast(seldocview.layoutDoc._width); - seldocview.rootDoc.rotateCenterY = final.y / NumCast(seldocview.layoutDoc._height); - + this.setRotateCenter(seldocview, [this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]]); return false; }), // moveEvent - action(() => {}), // upEvent + action(action(() => (this._isRotating = false))), // upEvent action((e, doubleTap) => { if (doubleTap) { seldocview.rootDoc.rotateCenterX = 0.5; @@ -342,8 +346,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const rcScreen = { X: this.rotCenter[0], Y: this.rotCenter[1] }; const rotateUndo = UndoManager.StartBatch('rotatedown'); const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke); - const centerPoint = { X: (this.Bounds.x + this.Bounds.r) / 2, Y: (this.Bounds.y + this.Bounds.b) / 2 }; + const centerPoint = this.rotCenter.slice(); const infos = new Map(); + const seldocview = SelectionManager.Views()[0]; SelectionManager.Views().forEach(dv => { const accumRot = (NumCast(dv.rootDoc._jitterRotation) / 180) * Math.PI; const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y); @@ -372,18 +377,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] }; const deltaAng = InkStrokeProperties.angleChange(movedPoint, previousPoint, rcScreen); if (selectedInk.length) { - deltaAng && InkStrokeProperties.Instance.rotateInk(selectedInk, -deltaAng, centerPoint); + deltaAng && InkStrokeProperties.Instance.rotateInk(selectedInk, deltaAng, rcScreen); + this.setRotateCenter(seldocview, centerPoint); } else { infoRot(deltaAng); } return false; }, // moveEvent action(() => { - const dv = SelectionManager.Views()[0]; - const oldRotation = NumCast(dv.rootDoc._jitterRotation); + const oldRotation = NumCast(seldocview.rootDoc._jitterRotation); const diff = oldRotation - Math.round(oldRotation / 45) * 45; if (Math.abs(diff) < 5) { - infoRot(((Math.round(oldRotation / 45) * 45) / 180) * Math.PI, true); + if (selectedInk.length) { + InkStrokeProperties.Instance.rotateInk(selectedInk, ((Math.round(oldRotation / 45) * 45 - oldRotation) / 180) * Math.PI, rcScreen); + } else { + infoRot(((Math.round(oldRotation / 45) * 45) / 180) * Math.PI, true); + } + } + if (selectedInk.length) { + this.setRotateCenter(seldocview, centerPoint); } this._isRotating = false; rotateUndo?.end(); @@ -830,9 +842,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P top: this.Bounds.y, pointerEvents: 'none', }}> -
e.preventDefault()}> - } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> -
+ {this._isRotating ? null : ( +
e.preventDefault()}> + } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> +
+ )}
{!this._showRotCenter ? null : (
Date: Thu, 27 Oct 2022 15:57:09 -0400 Subject: cleaned up treeViews to work across PresBox/ppt slides/collections/ - modified DocumentView rendering to remove pres box refs. fixed mainView width. --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 8 +- src/client/views/DocumentDecorations.tsx | 18 +- src/client/views/LightboxView.tsx | 26 +-- src/client/views/MainView.tsx | 1 + src/client/views/StyleProvider.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 2 + .../views/collections/CollectionTreeView.scss | 2 - .../views/collections/CollectionTreeView.tsx | 62 ++++--- src/client/views/collections/CollectionView.tsx | 2 + src/client/views/collections/TabDocView.tsx | 1 - src/client/views/collections/TreeView.scss | 3 +- src/client/views/collections/TreeView.tsx | 179 +++++++++---------- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 37 ++-- src/client/views/nodes/FieldView.tsx | 2 - src/client/views/nodes/VideoBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 +- src/client/views/nodes/trails/PresBox.tsx | 7 +- src/client/views/nodes/trails/PresElementBox.scss | 14 +- src/client/views/nodes/trails/PresElementBox.tsx | 197 ++++++++++++--------- src/fields/Doc.ts | 3 + 23 files changed, 301 insertions(+), 290 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d82ffdf9..bbb896d6e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -335,7 +335,6 @@ export class DocumentOptions { treeViewHideHeaderIfTemplate?: boolean; // whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox) treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. - treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically treeViewChildDoubleClick?: ScriptField; // // Action Button buttonMenu?: boolean; // whether a action button should be displayed @@ -395,7 +394,6 @@ export namespace Docs { _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, - treeViewGrowsHorizontally: true, nativeHeightUnfrozen: true, forceReflow: true, links: '@links(self)', @@ -570,7 +568,7 @@ export namespace Docs { DocumentType.SLIDER, { layout: { view: SliderBox, dataField: defaultDataKey }, - options: { links: '@links(self)', treeViewGrowsHorizontally: true }, + options: { links: '@links(self)' }, }, ], [ diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2bc464127..a2974177e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -480,7 +480,7 @@ export class CurrentUserUtils { const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters const reqdOpts:DocumentOptions = { title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true, - targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, treeViewTruncateTitleWidth: 150, ignoreClick: true, + targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, treeViewTruncateTitleWidth: 350, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias", _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true, contextMenuLabels:new List(contextMenuLabels), @@ -518,7 +518,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true, isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true, - treeViewTruncateTitleWidth: 150, ignoreClick: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 350, ignoreClick: true, childDropAction: "alias", childContextMenuLabels: new List(["Create new folder"]), childContextMenuIcons: new List(["plus"]), explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." @@ -535,7 +535,7 @@ export class CurrentUserUtils { static setupRecentlyClosed(doc: Doc, field:string) { const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true, - treeViewTruncateTitleWidth: 150, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", + treeViewTruncateTitleWidth: 350, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", contextMenuLabels: new List(["Empty recently closed"]), contextMenuIcons:new List(["trash"]), explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." @@ -561,7 +561,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view", boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, - treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 + treeViewHideTitle: true, treeViewTruncateTitleWidth: 350 }; if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }); return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 10517d829..06eb6c6d7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -112,7 +112,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } } //@ts-ignore - const titleField = +this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle; + const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle; Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true); if (d.rootDoc.syncLayoutFieldWithTitle) { @@ -345,7 +345,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => { const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]); const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; - const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI); + const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI); seldocview.rootDoc.rotateCenterX = final.x / NumCast(seldocview.layoutDoc._width); seldocview.rootDoc.rotateCenterY = final.y / NumCast(seldocview.layoutDoc._height); }; @@ -381,7 +381,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const infos = new Map(); const seldocview = SelectionManager.Views()[0]; SelectionManager.Views().forEach(dv => { - const accumRot = (NumCast(dv.rootDoc._jitterRotation) / 180) * Math.PI; + const accumRot = (NumCast(dv.rootDoc._rotation) / 180) * Math.PI; const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y); const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.rootDoc.width) / 2, localRotCtr[1] - NumCast(dv.rootDoc.height) / 2]; const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot); @@ -396,7 +396,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle }); dv.rootDoc.x = infos.get(dv.rootDoc)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x); dv.rootDoc.y = infos.get(dv.rootDoc)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y); - dv.rootDoc._jitterRotation = ((isAbs ? 0 : NumCast(dv.rootDoc._jitterRotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360 + dv.rootDoc._rotation = ((isAbs ? 0 : NumCast(dv.rootDoc._rotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360 }) ); }; @@ -416,7 +416,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P return false; }, // moveEvent action(() => { - const oldRotation = NumCast(seldocview.rootDoc._jitterRotation); + const oldRotation = NumCast(seldocview.rootDoc._rotation); const diff = oldRotation - Math.round(oldRotation / 45) * 45; if (Math.abs(diff) < 5) { if (selectedInk.length) { @@ -611,7 +611,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } } else { const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2]; - const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._jitterRotation) / 180) * Math.PI); + const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI); const maxHeight = doc.nativHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH); @@ -619,7 +619,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P dH && (doc._autoHeight = false); const rotCtr2 = [NumCast(doc._width) / 2, NumCast(doc._height) / 2]; - const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._jitterRotation) / 180) * Math.PI); + const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._rotation) / 180) * Math.PI); doc.x = NumCast(doc.x) + tlRotated.x + rotCtr[0] - (tlRotated2.x + rotCtr2[0]); // doc shifts by amount topleft moves because rotation is about center of doc doc.y = NumCast(doc.y) + tlRotated.y + rotCtr[1] - (tlRotated2.y + rotCtr2[1]); } @@ -695,7 +695,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const loccenter = Utils.rotPt( NumCast(seldocview.rootDoc.rotateCenterX) * NumCast(seldocview.layoutDoc._width), NumCast(seldocview.rootDoc.rotateCenterY) * NumCast(seldocview.layoutDoc._height), - (NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI + (NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI ); return seldocview.props .ScreenToLocalTransform() @@ -783,7 +783,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION; // when do we want an object to not rotate? - const rotation = NumCast(seldocview.rootDoc._jitterRotation); + const rotation = NumCast(seldocview.rootDoc._rotation); const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index bd6cea28a..22b0380a2 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -289,29 +289,29 @@ export class LightboxView extends React.Component { })} Document={LightboxView.LightboxDoc} DataDoc={undefined} + PanelWidth={this.lightboxWidth} + PanelHeight={this.lightboxHeight} LayoutTemplate={LightboxView.LightboxDocTemplate} - addDocument={undefined} isDocumentActive={returnFalse} isContentActive={returnTrue} - addDocTab={this.addDocTab} - pinToPres={TabDocView.PinDoc} - onBrowseClick={MainView.Instance.exploreMode} + styleProvider={DefaultStyleProvider} + ScreenToLocalTransform={this.lightboxScreenToLocal} + renderDepth={0} rootSelected={returnTrue} docViewPath={returnEmptyDoclist} docFilters={this.docFilters} - removeDocument={undefined} - styleProvider={DefaultStyleProvider} - ScreenToLocalTransform={this.lightboxScreenToLocal} - PanelWidth={this.lightboxWidth} - PanelHeight={this.lightboxHeight} - focus={DocUtils.DefaultFocus} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} - renderDepth={0} + addDocument={undefined} + removeDocument={undefined} + whenChildContentsActiveChanged={emptyFunction} + addDocTab={this.addDocTab} + pinToPres={TabDocView.PinDoc} + bringToFront={emptyFunction} + onBrowseClick={MainView.Instance.exploreMode} + focus={DocUtils.DefaultFocus} />
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 03c0642ed..4dc1ebd99 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -647,6 +647,7 @@ export class MainView extends React.Component { e.preventDefault(); }} style={{ + width: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`, minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`, transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined, }}> diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 2eb3dd532..bc8bd7b7f 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -85,7 +85,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic'; const isBackground = () => doc && BoolCast(doc._lockedPosition); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); @@ -167,11 +166,11 @@ export function DefaultStyleProvider(doc: Opt, props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; + return Doc.IsComicStyle(doc) ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || (doc?.type === DocumentType.RTF && !showTitle()?.includes('noMargin')) || diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ba29b1d6f..77b47ed82 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -350,6 +350,8 @@ export class CollectionStackingView extends CollectionSubView = new Set(); // list of tree view items to monitor for height changes private observer: any; // observer for monitoring tree view items. - private static expandViewLabelSize = 20; @computed get doc() { return this.props.Document; @@ -100,7 +99,10 @@ export class CollectionTreeView extends CollectionSubView disposer?.()); } + shrinkWrap = () => {}; // placeholder to allow setContentView to work + componentDidMount() { + //this.props.setContentView?.(this); this._disposers.autoheight = reaction( () => this.rootDoc.autoHeight, auto => auto && this.computeHeight(), @@ -197,9 +199,7 @@ export class CollectionTreeView extends CollectionSubView void) => this.onExternalDrop(e, {}, addDocs); @undoBatch - makeTextCollection = (childDocs: Doc[]) => { - this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true); - }; + makeTextCollection = (childDocs: Doc[]) => this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true); get editableTitle() { return ( @@ -258,6 +258,7 @@ export class CollectionTreeView extends CollectionSubView ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; + headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields); @computed get treeViewElements() { TraceMobx(); const dropAction = StrCast(this.doc.childDropAction) as dropActionType; @@ -281,7 +282,7 @@ export class CollectionTreeView extends CollectionSubView this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields), + this.headerFields, [], this.props.onCheckedClick, this.onChildClick, @@ -304,7 +305,7 @@ export class CollectionTreeView extends CollectionSubView (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.props.ScreenToLocalTransform().Scale))} key={this.doc[Id]} - style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}}> + style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}> {this.outlineMode ? this.documentTitle : this.editableTitle}
); @@ -372,7 +373,7 @@ export class CollectionTreeView extends CollectionSubView (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.NativeDimScaling?.() || 1); + panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (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; @@ -396,7 +397,7 @@ export class CollectionTreeView extends CollectionSubView @@ -432,24 +431,29 @@ export class CollectionTreeView extends CollectionSubView - {this.contentFunc} - - ) : ( - this.contentFunc() + const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1) || 1; + return ( +
+ {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? ( + + {this.contentFunc} + + ) : ( + this.contentFunc() + )} +
); } } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index dcaad5632..9f63a11aa 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -54,6 +54,8 @@ interface CollectionViewProps_ extends FieldViewProps { childHideDecorationTitle?: () => boolean; childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection + childXPadding?: number; + childYPadding?: number; childLayoutString?: string; childIgnoreNativeSize?: boolean; childClickScript?: ScriptField; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index a6cfbd6b2..cde5132a3 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -256,7 +256,6 @@ export class TabDocView extends React.Component { pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc. pinDoc.treeViewFieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field pinDoc.treeViewExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view - 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; diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 57bb5274d..83fee013a 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -24,6 +24,7 @@ // width: $TREE_BULLET_WIDTH; width: 100%; height: 100%; + position: absolute; .treeView-expandIcon { display: none; @@ -118,7 +119,7 @@ display: flex; // needed for PresBox's treeView border: transparent 1px solid; align-items: center; - width: 100%; + width: max-content; border-radius: 5px; &:hover { diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index b9f86fa6b..1e97eee37 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -10,7 +10,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, simulateMouseClick, Utils } from '../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; @@ -321,6 +321,7 @@ export class TreeView extends React.Component { _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, + _fitWidth: true, treeViewType: TreeViewType.outline, x: 0, y: 0, @@ -398,29 +399,22 @@ export class TreeView extends React.Component { }; docTransform = () => this.refTransform(this._dref?.ContentRef?.current); getTransform = () => this.refTransform(this._tref.current); - docWidth = () => { - const layoutDoc = this.layoutDoc; - 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.NativeDimScaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]()); - }; - docHeight = () => { - const layoutDoc = this.layoutDoc; - return Math.max( - 70, - Math.min( - this.MAX_EMBED_HEIGHT, - (() => { - const aspect = Doc.NativeAspect(layoutDoc); - if (aspect) return this.docWidth() / (aspect || 1); - return layoutDoc._fitWidth - ? !Doc.NativeHeight(this.doc) - ? NumCast(this.props.containerCollection._height) - : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) - : layoutDoc[HeightSym]() || 50; - })() - ) + embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1); + embeddedPanelHeight = () => { + console.log(this.props.treeView.rootDoc.title); + const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + return Math.min( + layoutDoc[HeightSym](), + this.MAX_EMBED_HEIGHT, + (() => { + const aspect = Doc.NativeAspect(layoutDoc); + if (aspect) return this.embeddedPanelWidth() / (aspect || 1); + return layoutDoc._fitWidth + ? !Doc.NativeHeight(layoutDoc) + ? NumCast(layoutDoc._height) //this.props.containerCollection._height) + : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) + : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym](); + })() ); }; @@ -512,28 +506,6 @@ export class TreeView extends React.Component { return rows; } - 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.NativeDimScaling?.() || 1); - }; - rtfHeight = () => { - const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; - return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; - }; - rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth()); - expandPanelHeight = () => { - if (this.layoutDoc._fitWidth) return this.docHeight(); - const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym](); - const docAspect = this.docWidth() / this.docHeight(); - return docAspect < aspect ? this.docWidth() / aspect : this.docHeight(); - }; - expandPanelWidth = () => { - if (this.layoutDoc._fitWidth) return this.docWidth(); - const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym](); - const docAspect = this.docWidth() / this.docHeight(); - return docAspect > aspect ? this.docHeight() * aspect : this.docWidth(); - }; - @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; @@ -792,15 +764,18 @@ export class TreeView extends React.Component { titleStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView + const treeView = this.props.treeView; // prettier-ignore switch (property.split(':')[0]) { case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1; case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined; + case StyleProp.Hidden: return false; + case StyleProp.BoxShadow: return undefined; case StyleProp.DocContents: const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc); const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; - return this.props.treeView.outlineMode ? null : ( + return treeView.outlineMode ? null : (
{ maxWidth: props?.PanelWidth() || undefined, background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor), outline: `solid ${highlightColor} ${highlightIndex}px`, + paddingLeft: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)), + paddingRight: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)), + paddingTop: treeView.props.childYPadding, + paddingBottom: treeView.props.childYPadding, }}> {StrCast(doc?.title)}
); } - return this.props?.treeView?.props.styleProvider?.(doc, props, property); + return treeView.props.styleProvider?.(doc, props, property); }; embeddedStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (property.startsWith(StyleProp.Decorations)) return null; + if (property.startsWith(StyleProp.Hidden)) return false; return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { @@ -843,7 +823,7 @@ export class TreeView extends React.Component { } return false; }; - titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth())); + titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - 3 * treeBulletWidth(); return18 = () => 18; /** @@ -851,6 +831,7 @@ export class TreeView extends React.Component { */ @computed get renderTitle() { + // TraceMobx(); const view = this._editTitle ? ( { } })} Document={this.doc} + fitWidth={(doc: Doc) => true} DataDoc={undefined} scriptContext={this} hideDecorationTitle={this.props.treeView.outlineMode} @@ -905,7 +887,7 @@ export class TreeView extends React.Component { removeDocument={this.props.removeDoc} ScreenToLocalTransform={this.getTransform} NativeHeight={this.return18} - NativeWidth={this.titleWidth} + NativeWidth={returnZero} PanelWidth={this.titleWidth} PanelHeight={this.return18} contextMenuItems={this.contextMenuItems} @@ -918,10 +900,12 @@ export class TreeView extends React.Component { disableDocBrushing={this.props.treeView.props.disableDocBrushing} hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} + xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)} + yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} + ContainingCollectionView={this.props.treeView.props.CollectionView} ContainingCollectionDoc={this.props.treeView.props.Document} /> ); @@ -968,53 +952,52 @@ export class TreeView extends React.Component { }; renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { - const isExpandable = this.doc._treeViewGrowsHorizontally; - const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth; - const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight; return ( - (this._dref = r))} - Document={this.doc} - DataDoc={undefined} - PanelWidth={panelWidth} - PanelHeight={panelHeight} - NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} - NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} - LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} - LayoutTemplate={this.props.treeView.props.childLayoutTemplate} - isContentActive={isActive} - isDocumentActive={isActive} - styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} - hideTitle={asText} - fitContentsToBox={returnTrue} - hideDecorationTitle={this.props.treeView.outlineMode} - hideResizeHandles={this.props.treeView.outlineMode} - onClick={this.onChildClick} - focus={this.refocus} - onKey={this.onKeyDown} - hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} - dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} - ScreenToLocalTransform={this.docTransform} - renderDepth={this.props.renderDepth + 1} - treeViewDoc={this.props.treeView?.props.Document} - rootSelected={returnTrue} - docViewPath={this.props.treeView.props.docViewPath} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containerCollection} - ContainingCollectionView={undefined} - addDocument={this.props.addDocument} - moveDocument={this.move} - removeDocument={this.props.removeDoc} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.treeView.props.pinToPres} - disableDocBrushing={this.props.treeView.props.disableDocBrushing} - bringToFront={returnFalse} - scriptContext={this} - /> +
+ (this._dref = r))} + Document={this.doc} + DataDoc={undefined} + PanelWidth={this.embeddedPanelWidth} + PanelHeight={this.embeddedPanelHeight} + LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} + LayoutTemplate={this.props.treeView.props.childLayoutTemplate} + isContentActive={isActive} + isDocumentActive={isActive} + styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} + hideTitle={asText} + //fitContentsToBox={returnTrue} + hideDecorationTitle={this.props.treeView.outlineMode} + hideResizeHandles={this.props.treeView.outlineMode} + onClick={this.onChildClick} + focus={this.refocus} + onKey={this.onKeyDown} + hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} + dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} + ScreenToLocalTransform={this.docTransform} + renderDepth={this.props.renderDepth + 1} + treeViewDoc={this.props.treeView?.props.Document} + rootSelected={returnTrue} + docViewPath={this.props.treeView.props.docViewPath} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.containerCollection} + ContainingCollectionView={undefined} + addDocument={this.props.addDocument} + moveDocument={this.move} + removeDocument={this.props.removeDoc} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)} + yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)} + addDocTab={this.props.addDocTab} + pinToPres={this.props.treeView.props.pinToPres} + disableDocBrushing={this.props.treeView.props.disableDocBrushing} + bringToFront={returnFalse} + scriptContext={this} + /> +
); }; @@ -1148,7 +1131,7 @@ export class TreeView extends React.Component { } const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); - const rowWidth = () => panelWidth() - treeBulletWidth(); + const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1); const treeViewRefs = new Map(); return docs .filter(child => child instanceof Doc) diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 559951efc..2f246e74f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1317,7 +1317,7 @@ export class CollectionFreeFormView extends CollectionSubView ); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 04c7b96e3..260c98816 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -25,7 +25,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; highlight?: boolean; - jitterRotation: number; + rotation: number; dataTransition?: string; replica: string; CollectionFreeFormView: CollectionFreeFormView; @@ -38,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent void; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; + xPadding?: number; + yPadding?: number; dropAction?: dropActionType; dontRegisterView?: boolean; hideLinkButton?: boolean; @@ -1349,17 +1352,10 @@ export class DocumentViewInternal extends DocComponent - {!this.headerMargin ? ( - <> - {' '} - {this.contents} {titleView}{' '} - - ) : ( - <> - {' '} - {titleView} {this.contents}{' '} - - )} + {' '} + {!this.headerMargin ? this.contents : titleView} + {!this.headerMargin ? titleView : this.contents} + {' ' /* */} {captionView}
); @@ -1372,7 +1368,7 @@ export class DocumentViewInternal extends DocComponent {animRenderDoc}
- +
@@ -1552,7 +1548,7 @@ export class DocumentView extends React.Component { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.fitWidth)); } @computed get shouldNotScale() { - return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); + return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || [CollectionViewType.Docking].includes(this.Document._viewType as any); } @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width); @@ -1597,7 +1593,7 @@ export class DocumentView extends React.Component { 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())) { + if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; } const xf = this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse(); @@ -1687,10 +1683,9 @@ export class DocumentView extends React.Component { render() { TraceMobx(); - 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; + 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 isButton = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return (
{!this.props.Document || !this.props.PanelWidth() ? null : ( @@ -1700,11 +1695,11 @@ export class DocumentView extends React.Component { style={{ transition: this.props.dataTransition, 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()}%`, + width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, height: isButton || this.props.forceAutoHeight ? undefined - : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`), + : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`), }}> this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight() ); + this._disposers.width = reaction( + () => this.props.PanelWidth(), + width => this.tryUpdateScrollHeight() + ); this._disposers.scrollHeight = reaction( () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), - ({ width, scrollHeight, autoHeight }) => { - width && autoHeight && this.resetNativeHeight(scrollHeight); - }, + ({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight), { fireImmediately: true } ); this._disposers.componentHeights = reaction( diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 258dad39c..4e8aed8a6 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -429,7 +429,7 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presPinLayout = true; pinDoc.presX = NumCast(targetDoc.x); pinDoc.presY = NumCast(targetDoc.y); - pinDoc.presRot = NumCast(targetDoc.jitterRotation); + pinDoc.presRot = NumCast(targetDoc.rotation); pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); } @@ -521,7 +521,7 @@ export class PresBox extends ViewBoxBaseComponent() { targetDoc._dataTransition = presTransitionTime; targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x)); targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y)); - targetDoc.jitterRotation = NumCast(activeItem.presRot, NumCast(targetDoc.jitterRotation)); + targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation)); targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width)); targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height)); setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10); @@ -2652,9 +2652,10 @@ export class PresBox extends ViewBoxBaseComponent() { PanelHeight={this.panelHeight} childIgnoreNativeSize={true} moveDocument={returnFalse} - childFitWidth={returnTrue} + //childFitWidth={returnTrue} childOpacity={returnOne} childLayoutTemplate={this.childLayoutTemplate} + childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined} filterAddDocument={this.addDocumentFilter} removeDocument={returnFalse} dontRegisterView={true} diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss index 8a6c2a6dc..415253af1 100644 --- a/src/client/views/nodes/trails/PresElementBox.scss +++ b/src/client/views/nodes/trails/PresElementBox.scss @@ -42,10 +42,7 @@ $slide-active: #5b9fdd; width: 100%; border-bottom: 0.5px solid grey; display: flex; - justify-content: space-between; align-items: center; - grid-template-rows: 16px 10px auto; - grid-template-columns: max-content max-content max-content max-content auto; .presItem-name { display: flex; @@ -102,13 +99,7 @@ $slide-active: #5b9fdd; grid-row: 3; grid-column: 1/8; position: relative; - display: flex; - width: auto; - justify-content: center; - margin: auto; - margin-bottom: 2px; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; + display: inline-block; } .presItem-embeddedMask { @@ -124,8 +115,7 @@ $slide-active: #5b9fdd; .presItem-slideButtons { display: flex; - grid-column: 7; - grid-row: 1/3; + position: absolute; width: max-content; justify-self: right; justify-content: flex-end; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index f4ab845f3..5d14a0e9a 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -4,7 +4,9 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx' import { observer } from 'mobx-react'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; import { Copy, Id } from '../../../../fields/FieldSymbols'; +import { InkField } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; +import { RichTextField } from '../../../../fields/RichTextField'; import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -14,6 +16,7 @@ import { DragManager } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; +import { MarqueeView } from '../../collections/collectionFreeForm'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; @@ -24,9 +27,6 @@ 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'; -import { MarqueeView } from '../../collections/collectionFreeForm'; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. @@ -44,8 +44,11 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get indexInPres() { return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements) + @computed get expandViewHeight() { + return 100; + } @computed get collapsedHeight() { - return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(this.presBox._viewType as any) ? 35 : 31; + return 35; } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up @computed get presStatus() { return this.presBox.presStatus; @@ -58,7 +61,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined; } @computed get presBox() { - return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!; + return this.props.ContainingCollectionDoc!; } @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; @@ -67,8 +70,8 @@ export class PresElementBox extends ViewBoxBaseComponent() { componentDidMount() { this.layoutDoc.hideLinkButton = true; this._heightDisposer = reaction( - () => [this.rootDoc.presExpandInlineButton, this.collapsedHeight], - params => (this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0)), + () => ({ expand: this.rootDoc.presExpandInlineButton, height: this.collapsedHeight }), + ({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)), { fireImmediately: true } ); } @@ -84,10 +87,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { @action presExpandDocumentClick = () => (this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton); - embedHeight = (): number => 97; + embedHeight = (): number => this.collapsedHeight + this.expandViewHeight; // embedWidth = () => this.props.PanelWidth(); // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight); - embedWidth = (): number => this.props.PanelWidth() - 35; + embedWidth = (): number => this.props.PanelWidth() / 2; styleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (property === StyleProp.Opacity) return 1; return this.props.styleProvider?.(doc, props, property); @@ -98,35 +101,35 @@ export class PresElementBox extends ViewBoxBaseComponent() { */ @computed get renderEmbeddedInline() { return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : ( -
+
-
+ {/*
*/}
); } @@ -206,8 +209,8 @@ export class PresElementBox extends ViewBoxBaseComponent() { 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; - dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument; + dragData.treeViewDoc = this.presBox._viewType === CollectionViewType.Tree ? this.props.ContainingCollectionDoc : undefined; // this.props.DocumentView?.()?.props.treeViewDoc; + dragData.moveDocument = this.props.moveDocument; const dragItem: HTMLElement[] = []; if (dragArray.length === 1) { const doc = this._itemRef.current || dragArray[0]; @@ -307,7 +310,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { updateCapturedContainerLayout = (targetDoc: Doc, activeItem: Doc) => { activeItem.presX = NumCast(targetDoc.x); activeItem.presY = NumCast(targetDoc.y); - activeItem.presRot = NumCast(targetDoc.jitterRotation); + activeItem.presRot = NumCast(targetDoc.rotation); activeItem.presWidth = NumCast(targetDoc.width); activeItem.presHeight = NumCast(targetDoc.height); }; @@ -464,16 +467,90 @@ export class PresElementBox extends ViewBoxBaseComponent() { return width; } + @computed get presButtons() { + const presBox: Doc = this.presBox; //presBox + const presBoxColor: string = StrCast(presBox._backgroundColor); + const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false; + const targetDoc: Doc = this.targetDoc; + const activeItem: Doc = this.rootDoc; + + const items: JSX.Element[] = []; + if (activeItem.presPinLayout) { + items.push( + Update captured doc layout
}> +
this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}> + L +
+ + ); + } + if (activeItem.presPinData || activeItem.presPinView) { + items.push( + Update captured doc content
}> +
this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}> + C +
+ + ); + } + if (!Doc.noviceMode) { + items.push( + {this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}
}> +
(this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> + e.stopPropagation()} /> +
+ + ); + } + if (this.indexInPres === 0) { + items.push( + {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()} /> +
+
+ + ); + } + items.push( + {this.rootDoc.presExpandInlineButton ? 'Minimize' : 'Expand'}
}> +
{ + e.stopPropagation(); + this.presExpandDocumentClick(); + }}> + e.stopPropagation()} /> +
+ + ); + items.push( + Remove from presentation
}> +
+ e.stopPropagation()} /> +
+ + ); + return items; + } + @computed get mainItem() { const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false; const isCurrent: boolean = this.presBox._itemIndex === this.indexInPres; - const toolbarWidth: number = this.toolbarWidth; - const showMore: boolean = this.toolbarWidth >= 300; const miniView: boolean = this.toolbarWidth <= 110; const presBox: Doc = this.presBox; //presBox const presBoxColor: string = StrCast(presBox._backgroundColor); const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false; - const targetDoc: Doc = this.targetDoc; const activeItem: Doc = this.rootDoc; return ( @@ -484,6 +561,10 @@ export class PresElementBox extends ViewBoxBaseComponent() { style={{ backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent', opacity: this._dragging ? 0.3 : 1, + paddingLeft: NumCast(this.layoutDoc._xPadding, this.props.xPadding), + paddingRight: NumCast(this.layoutDoc._xPadding, this.props.xPadding), + paddingTop: NumCast(this.layoutDoc._yPadding, this.props.yPadding), + paddingBottom: NumCast(this.layoutDoc._yPadding, this.props.yPadding), }} onClick={e => { e.stopPropagation(); @@ -507,6 +588,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { ref={this._dragRef} className={`presItem-slide ${isCurrent ? 'active' : ''}`} style={{ + display: 'infline-block', backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), //boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined, border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined, @@ -514,8 +596,9 @@ export class PresElementBox extends ViewBoxBaseComponent() {
{`${this.indexInPres + 1}. `}
@@ -523,56 +606,8 @@ export class PresElementBox extends ViewBoxBaseComponent() {
{/*
{"Movement speed"}
}>
{this.transition}
*/} {/*
{"Duration"}
}>
{this.duration}
*/} -
- Update captured doc layout
}> -
this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinLayout ? 'flex' : 'none' }}> - L -
- - Update captured doc content
}> -
this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinData || activeItem.presPinView ? 'flex' : 'none' }}> - C -
- - {!Doc.noviceMode && ( - {this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}
}> -
(this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> - e.stopPropagation()} /> -
- - )} - {activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}
}> -
(activeItem.groupWithUp = !activeItem.groupWithUp)} - style={{ - display: this.indexInPres === 0 ? 'none' : '', - 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.rootDoc.presExpandInlineButton ? 'Minimize' : 'Expand'}
}> -
{ - e.stopPropagation(); - this.presExpandDocumentClick(); - }}> - e.stopPropagation()} /> -
- - Remove from presentation
}> -
- e.stopPropagation()} /> -
- +
+ {...this.presButtons}
{this.renderEmbeddedInline}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 466b43287..fc43325fe 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -238,6 +238,9 @@ export class Doc extends RefField { Doc.UserDoc().activePage = val; DocServer.UPDATE_SERVER_CACHE(); } + public static IsComicStyle(doc?: Doc) { + return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic'; + } public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } -- cgit v1.2.3-70-g09d2 From 470af89b6ddc17dda613705d599a0ff221d0b927 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 31 Oct 2022 09:24:23 -0400 Subject: cleaned up collection linear view a bit. --- src/client/documents/Documents.ts | 10 +- src/client/util/CurrentUserUtils.ts | 6 +- src/client/views/StyleProvider.tsx | 3 +- .../collectionLinear/CollectionLinearView.scss | 8 +- .../collectionLinear/CollectionLinearView.tsx | 133 ++++++++++----------- 5 files changed, 77 insertions(+), 83 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bbb896d6e..d789d4ee3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -261,7 +261,7 @@ export class DocumentOptions { presDuration?: number; //the duration of the slide in presentation view presProgressivize?: boolean; borderRounding?: string; - boxShadow?: string; + boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow data?: any; baseProto?: boolean; // is this a base prototoype dontRegisterView?: boolean; @@ -298,7 +298,6 @@ export class DocumentOptions { linearViewExpandable?: boolean; // can linear view be expanded linearViewToggleButton?: string; // button to open close linear view group linearViewSubMenu?: boolean; - linearViewFloating?: boolean; flexGap?: number; // Linear view flex gap flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse'; @@ -1384,11 +1383,12 @@ export namespace DocUtils { ); } - export function AssignScripts(doc: Doc, scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { + export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) { scripts && Object.keys(scripts).map(key => { - if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript(scripts[key], { + const script = scripts[key]; + if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) { + doc[key] = ScriptField.MakeScript(script, { dragData: DragManager.DocumentDragData.name, value: 'any', _readOnly_: 'boolean', diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a2974177e..114dbac93 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -582,20 +582,22 @@ export class CurrentUserUtils { /// initializes the required buttons in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc, field="myDockedBtns") { const dockedBtns = DocCast(doc[field]); - const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}, funcs?: {[key:string]:string}) => + const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) => DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? CurrentUserUtils.createToolButton(opts), scripts, funcs); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }}, { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }}, + { scripts: { }, opts: { title: "linker", icon: "linkui", toolTip: "link started"}}, + { scripts: { }, opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing audio"}}, // { scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, opts: { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, width: 20}}, // { scripts: { onClick:""}, opts: { title: "Num", icon: "", toolTip: "Frame Number", btnType: ButtonType.TextButton, width: 20}, funcs: { buttonText: 'selectedDocs()?.lastElement()?.currentFrame.toString()'}}, // { scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, opts:{title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, width: 20,} }, ]; const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts = { - title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, + title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }; reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index bc8bd7b7f..869570a17 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -18,6 +18,7 @@ import { FieldViewProps } from './nodes/FieldView'; import { SliderBox } from './nodes/SliderBox'; import './StyleProvider.scss'; import React = require('react'); +import { Shadows } from 'browndash-components'; export enum StyleProp { TreeViewIcon = 'treeViewIcon', @@ -250,7 +251,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { + return !DocumentLinksButton.StartLink ? null : ( + e.stopPropagation()}> + + Creating link from:{' '} + + {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') + + (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')} + + + + {'Toggle description pop-up'}
} placement="top"> + + Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'} + + + + Exit linking mode
} placement="top"> + + Stop + + + + ); + }; + getCurrentlyPlayingUI = () => { + return !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : ( + + + Currently playing: + {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( + DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> + {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} + + ))} + + + ); + }; getDisplayDoc = (doc: Doc, preview: boolean = false) => { + if (doc.icon === 'linkui') return this.getLinkUI(); + if (doc.icon === 'currentlyplayingui') return this.getCurrentlyPlayingUI(); + const nested = doc._viewType === CollectionViewType.Linear; const hidden = doc.hidden === true; @@ -172,40 +214,32 @@ export class CollectionLinearView extends CollectionSubView() { }; render() { - const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label - const flexDir: any = StrCast(this.Document.flexDirection); // Specify direction of linear view content - const flexGap: number = NumCast(this.Document.flexGap); // Specify the gap between linear view content - const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); // Specify whether it is expandable or not - const floating: boolean = BoolCast(this.props.Document.linearViewFloating); // Specify whether it is expandable or not + const flexDir = StrCast(this.Document.flexDirection); // Specify direction of linear view content + const flexGap = NumCast(this.Document.flexGap); // Specify the gap between linear view content + const isExpanded = BoolCast(this.layoutDoc.linearViewIsExpanded); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle const menuOpener = ( ); return ( -
+
- {!expandable ? null : ( - {BoolCast(this.props.Document.linearViewIsExpanded) ? 'Close' : 'Open'}
} placement="top"> + {!this.props.Document.linearViewExpandable ? null : ( + {isExpanded ? 'Close' : 'Open'}
} placement="top"> {menuOpener} )} { ScriptCast(this.Document.onClick)?.script.run({ @@ -216,7 +250,7 @@ export class CollectionLinearView extends CollectionSubView() { thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.docViewPath().lastElement(), }); - this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked; + this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked; })} /> @@ -224,58 +258,11 @@ export class CollectionLinearView extends CollectionSubView() { className="collectionLinearView-content" style={{ height: this.dimension(), - flexDirection: flexDir, + flexDirection: flexDir as any, gap: flexGap, }}> {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
- {!DocumentLinksButton.StartLink || this.layoutDoc !== Doc.MyDockedBtns ? null : ( - e.stopPropagation()}> - - Creating link from:{' '} - - {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') + - (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')} - - - - -
{'Toggle description pop-up'}
- - } - placement="top"> - - Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'} - -
- - -
Exit linking mode
- - } - placement="top"> - - Stop - -
-
- )} - {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== Doc.MyDockedBtns ? null : ( - - - Currently playing: - {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( - DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> - {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} - - ))} - - - )}
); -- cgit v1.2.3-70-g09d2 From bbc993dd70d105c3de078208763e6415eedb1ff7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 10 Nov 2022 11:18:46 -0500 Subject: partial fix for previewing multiple links to different sidebar notes. fixed opening sidebar to create an annotation the first time. fixed honoring dashFieldView's non-editable flag. moved isLinkButton setter to expert and make isLinkButton default for Buttons. fixed following Links with Zoom to reset view. fixed view for editing onClick for documents --- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/LinkFollower.ts | 5 +++-- src/client/views/DocumentButtonBar.tsx | 4 ++-- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/LinkDocPreview.tsx | 12 +++++++----- src/client/views/nodes/formattedText/DashFieldView.tsx | 3 +++ src/client/views/nodes/formattedText/FormattedTextBox.tsx | 13 ++++++++----- src/fields/Proxy.ts | 2 +- 10 files changed, 28 insertions(+), 19 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d789d4ee3..f1b8c3034 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1607,7 +1607,7 @@ export namespace DocUtils { const iconViews = DocListCast(Cast(Doc.UserDoc()['template-icons'], Doc, null)?.data); const templBtns = DocListCast(Cast(Doc.UserDoc()['template-buttons'], Doc, null)?.data); const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data); - const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data); + const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template-clickFuncs'], Doc, null)?.data); const allTemplates = iconViews .concat(templBtns) .concat(noteTypes) diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f2d851a40..d13209c4f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -267,7 +267,7 @@ export class CurrentUserUtils { {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _showSidebar: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, - {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, + {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }}, {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,}}, diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index ea0531fa2..43b2caf88 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -48,9 +48,10 @@ export class LinkFollower { }, }); } else { - res(where !== 'inPlace' ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target + finished?.(); + res(where !== 'inPlace' || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target } - }); + }, 100); }); if (!sourceDoc.followLinkZoom) { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index ce77b7446..3f12c7c9d 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -356,7 +356,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV {'Click to record 5 second annotation'}
}>
{ this._isRecording = true; @@ -463,7 +463,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV {this.metadataButton}
*/ } - {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
} + {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null :
{this.shareButton}
} {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : ( diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cec03c991..3fac137fe 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -767,7 +767,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); const useLock = bounds.r - bounds.x > 135 && seldocview.props.CollectionFreeFormDocumentView; - const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION; // when do we want an object to not rotate? + const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate? const rotation = NumCast(seldocview.rootDoc._rotation); const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4ee871ba2..973363f78 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -618,7 +618,7 @@ export class DocumentViewInternal extends DocComponent { _linkDocRef = React.createRef(); @observable public static LinkInfo: Opt; @observable _targetDoc: Opt; + @observable _markerTargetDoc: Opt; @observable _linkDoc: Opt; @observable _linkSrc: Opt; @observable _toolTipText = ''; @@ -57,9 +58,9 @@ export class LinkDocPreview extends React.Component { } if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { // want to show annotation context document if annotation is not text - linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._targetDoc = anno))); + linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._markerTargetDoc = this._targetDoc = anno))); } else { - this._targetDoc = linkTarget; + this._markerTargetDoc = this._targetDoc = linkTarget; } this._toolTipText = ''; } @@ -105,10 +106,11 @@ export class LinkDocPreview extends React.Component { // automatic links specify the target in the link info, not the source const linkTarget = anchor; this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget); - this._targetDoc = linkTarget; + this._markerTargetDoc = this._targetDoc = linkTarget; } else { this._linkSrc = anchor; const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); + this._markerTargetDoc = linkTarget; this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; } this._toolTipText = ''; @@ -175,10 +177,10 @@ export class LinkDocPreview extends React.Component { return Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)); }; @computed get previewHeader() { - return !this._linkDoc || !this._targetDoc || !this._linkSrc ? null : ( + return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : (
- {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + '...' : StrCast(this._targetDoc.title)} + {StrCast(this._markerTargetDoc.title).length > 16 ? StrCast(this._markerTargetDoc.title).substr(0, 16) + '...' : StrCast(this._markerTargetDoc.title)}

{StrCast(this._linkDoc.description)}

diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 39b38add2..41b92a3c7 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -171,6 +171,9 @@ export class DashFieldViewInternal extends React.Component this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection'); + getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection', false); @action setupAnchorMenu = () => { @@ -239,7 +239,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { !this.layoutDoc.showSidebar && this.toggleSidebar(); - this._sidebarRef.current?.anchorMenuClick(this.getAnchor()); + setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created }; AnchorMenu.Instance.OnAudio = (e: PointerEvent) => { !this.layoutDoc.showSidebar && this.toggleSidebar(); @@ -891,9 +891,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type.name === schema.marks.splitter.name)) { const allAnchors = [{ href, title, anchorId: anchor[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allAnchors ?? [])); - const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location }); + const link = state.schema.marks.linkAnchor.create({ allAnchors, title, location, noPreview }); tr = tr.addMark(pos, pos + node.nodeSize, link); + selectedText += (node as Node).textContent; } }); this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false; + anchor.text = selectedText; return anchor; } return anchorDoc ?? this.rootDoc; @@ -1471,7 +1474,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent node that wraps the hyerlink while (target && !target.dataset?.targethrefs) target = target.parentElement; - FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview); + FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); } if (e.button === 0 && this.props.isSelected(true) && !e.altKey) { diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts index e924ef7a3..72ae13035 100644 --- a/src/fields/Proxy.ts +++ b/src/fields/Proxy.ts @@ -66,7 +66,7 @@ export class ProxyField extends ObjectField { const cached = DocServer.GetCachedRefField(this.fieldId) as T; if (cached !== undefined) { - this.cache = cached; + setTimeout(action(() => (this.cache = cached))); } else if (!this.promise) { this.promise = DocServer.GetRefField(this.fieldId).then( action((field: any) => { -- cgit v1.2.3-70-g09d2 From 3d8bd17fa4f031f2223840b336c313a18e7e87aa Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 14 Nov 2022 14:29:48 -0500 Subject: fixed following link 'inPlace' from within lightbox to do same thing as if out of lightbox. fixed contentActive behavior for multicolumn view. removed progressivize from presbox. --- src/client/documents/Documents.ts | 1 - src/client/util/DocumentManager.ts | 24 +- src/client/util/LinkFollower.ts | 7 +- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/LightboxView.tsx | 8 +- src/client/views/MainView.tsx | 2 +- src/client/views/PropertiesView.tsx | 14 - src/client/views/collections/TabDocView.tsx | 9 +- .../collectionFreeForm/CollectionFreeFormView.scss | 50 --- .../collectionFreeForm/CollectionFreeFormView.tsx | 54 +-- .../CollectionMulticolumnView.tsx | 10 +- .../CollectionMultirowView.tsx | 8 +- src/client/views/nodes/DocumentView.scss | 6 +- src/client/views/nodes/trails/PresBox.tsx | 475 +-------------------- 14 files changed, 75 insertions(+), 595 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f1b8c3034..e44004f45 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -259,7 +259,6 @@ export class DocumentOptions { appearFrame?: number; // the frame in which the document appears presTransition?: number; //the time taken for the transition TO a document presDuration?: number; //the duration of the slide in presentation view - presProgressivize?: boolean; borderRounding?: string; boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow data?: any; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index b046d950f..01ca24439 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -131,6 +131,7 @@ export class DocumentManager { return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); }; public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { + if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind, originatingDoc); const views = this.getDocumentViews(toFind).filter(view => view.rootDoc !== originatingDoc); return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); }; @@ -168,8 +169,7 @@ export class DocumentManager { presZoomScale?: number ): void => { originalTarget = originalTarget ?? targetDoc; - const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; - const docView = getFirstDocView(targetDoc, originatingDoc); + const docView = this.getFirstDocumentView(targetDoc, originatingDoc); const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? docView?.rootDoc ?? targetDoc : docView?.rootDoc ?? targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field var wasHidden = resolvedTarget.hidden; @@ -206,11 +206,11 @@ export class DocumentManager { } finished?.(); }; - const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && getFirstDocView(annotatedDoc); + const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && this.getFirstDocumentView(annotatedDoc); 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 + const targetDocContextView = (targetDocContext && this.getFirstDocumentView(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 const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView; if (annoContainerView) { if (annoContainerView.props.Document.layoutKey === 'layout_icon') { @@ -281,7 +281,7 @@ export class DocumentManager { } else { // no timecode means we need to find the context view and focus on our target const findView = (delay: number) => { - const retryDocView = getFirstDocView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it + const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it if (retryDocView) { // we found the target in the context. Doc.linkFollowHighlight(retryDocView.rootDoc); @@ -316,10 +316,20 @@ export class DocumentManager { } } // there's no context view so we need to create one first and try again when that finishes - const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget); createViewFunc( targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - finishFunc + () => + this.jumpToDocument( + targetDoc, + true, + (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), + docContext, + linkDoc, + true /* if target not found, get rid of context just created */, + undefined, + finished, + originalTarget + ) ); } } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 1608a77f2..c09c9d1c5 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -30,11 +30,10 @@ export class LinkFollower { const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { const createTabForTarget = (didFocus: boolean) => new Promise(res => { - const where = LightboxView.LightboxDoc ? 'lightbox' : StrCast(sourceDoc.followLinkLocation, followLoc); + const where = LightboxView.LightboxDoc ? 'inPlace' : StrCast(sourceDoc.followLinkLocation, followLoc); docViewProps.addDocTab(doc, where); setTimeout(() => { - const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; - const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. + const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. if (targDocView) { targDocView.props.focus(doc, { willZoom: BoolCast(sourceDoc.followLinkZoom, false), @@ -110,7 +109,7 @@ export class LinkFollower { containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; } const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext; - DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'lightbox'), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished); + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'inPlace'), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished); } } else { allFinished(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a110bf51a..36875290e 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -458,7 +458,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
) : null} - {Doc.noviceMode ? null :
{this.recordButton}
} { Doc.noviceMode ? null :
{this.templateButton}
/*
@@ -467,6 +466,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV } {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
+
{this.recordButton}
{!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null :
{this.shareButton}
} {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : (
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 91773419a..5660a34e9 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -4,7 +4,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { InkTool } from '../../fields/InkField'; -import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { List } from '../../fields/List'; +import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; @@ -141,6 +142,11 @@ export class LightboxView extends React.Component { } } public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc, openInTabFunc?: any) => { + const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; + if (inPlaceView) { + inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); + return true; + } LightboxView.openInTabFunc = openInTabFunc; SelectionManager.DeselectAll(); return LightboxView.SetLightboxDoc( diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9648a7807..987bfc23d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -690,11 +690,11 @@ export class MainView extends React.Component { switch (locationFields[0]) { default: case 'inPlace': + case 'lightbox': return LightboxView.AddDocTab(doc, location); case 'add': return CollectionDockingView.AddSplit(doc, locationParams); case 'dashboard': return DashboardView.openDashboard(doc); case 'close': return CollectionDockingView.CloseSplit(doc, locationParams); case 'fullScreen': return CollectionDockingView.OpenFullScreen(doc); - case 'lightbox': return LightboxView.AddDocTab(doc, location); case 'toggle': return CollectionDockingView.ToggleSplit(doc, locationParams); } }; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 842664402..1f161c01b 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -95,7 +95,6 @@ export class PropertiesView extends React.Component { //Pres Trails booleans: @observable openPresTransitions: boolean = false; - @observable openPresProgressivize: boolean = false; @observable openAddSlide: boolean = false; @observable openSlideOptions: boolean = false; @@ -1651,19 +1650,6 @@ export class PropertiesView extends React.Component { {this.openPresTransitions ?
{PresBox.Instance.transitionDropdown}
: null}
)} - {/* {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) :
-
this.openPresProgressivize = !this.openPresProgressivize)} - style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> -     Progressivize -
- -
-
- {this.openPresProgressivize ?
- {PresBox.Instance.progressivizeDropdown} -
: null} -
} */} {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
{ // // TabDocView.PinDoc(doc, { hidePresBox: true }); // return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); // } + case 'inPlace': + const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; + if (inPlaceView) { + inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); + return true; + } case 'lightbox': return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); case 'toggle': return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack); - case 'inPlace': case 'add': default: return CollectionDockingView.AddSplit(doc, locationParams, this.stack); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index d80fcdfc3..7a7ae3f40 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -96,37 +96,6 @@ border-color: #69a5db; } -.progressivizeButton { - position: absolute; - display: grid; - grid-template-columns: auto 20px auto; - transform: translate(-105%, 0); - align-items: center; - border: black solid 1px; - border-radius: 3px; - justify-content: center; - width: 40; - z-index: 30000; - height: 20; - overflow: hidden; - background-color: #d5dce2; - transition: all 1s; - - .progressivizeButton-prev:hover { - color: #5a9edd; - } - - .progressivizeButton-frame { - justify-self: center; - text-align: center; - width: 15px; - } - - .progressivizeButton-next:hover { - color: #5a9edd; - } -} - .resizable { background: rgba(0, 0, 0, 0.2); width: 100px; @@ -178,25 +147,6 @@ } } -.progressivizeMove-frame { - width: 20px; - border-radius: 2px; - z-index: 100000; - color: white; - text-align: center; - background-color: #5a9edd; - transform: translate(-110%, 110%); -} - -.progressivizeButton:hover { - box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5); - - .progressivizeButton-frame { - background-color: #5a9edd; - color: white; - } -} - .collectionFreeform-customText { position: absolute; text-align: center; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d44c2f160..e24b116d0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,5 +1,5 @@ import { Bezier } from 'bezier-js'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { DateField } from '../../../../fields/DateField'; @@ -71,6 +71,9 @@ export class CollectionFreeFormView extends CollectionSubView (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null); + @computed get marqueeView() { TraceMobx(); return ( @@ -1912,8 +1917,7 @@ export class CollectionFreeFormView extends CollectionSubView @@ -2039,8 +2043,7 @@ interface CollectionFreeFormViewPannableContentsProps { viewDefDivClick?: ScriptField; children: () => JSX.Element[]; transition?: string; - presPaths?: boolean; - progressivize?: boolean; + presPaths: () => JSX.Element | null; presPinView?: boolean; isAnnotationOverlay: boolean | undefined; isAnnotationOverlayScrollable: boolean | undefined; @@ -2093,42 +2096,11 @@ class CollectionFreeFormViewPannableContents extends React.Component -
-
-
-
-
-
-
- ); - } - } - - @computed get zoomProgressivize() { - return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : null; - } - - @computed get progressivize() { - return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : null; - } - @computed get presPaths() { - const presPaths = 'presPaths' + (this.props.presPaths ? '' : '-hidden'); - return !PresBox.Instance || !this.props.presPaths ? null : ( + return !this.props.presPaths() ? null : ( <> -
{PresBox.Instance.order}
- +
{PresBox.Instance?.order}
+ @@ -2140,7 +2112,7 @@ class CollectionFreeFormViewPannableContents extends React.Component - {PresBox.Instance.paths} + {this.props.presPaths()} ); @@ -2180,8 +2152,6 @@ class CollectionFreeFormViewPannableContents extends React.Component )} {this.presPaths} - {this.progressivize} - {this.zoomProgressivize}
); } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 465dbfe6d..e5a2d9007 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -9,7 +9,7 @@ import { DragManager, dropActionType } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; -import { DocumentView } from '../../nodes/DocumentView'; +import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionMulticolumnView.scss'; import ResizeBar from './MulticolumnResizer'; @@ -241,9 +241,9 @@ export class CollectionMulticolumnView extends CollectionSubView() { } 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; + focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); + isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); + isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return ( 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; + focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); + isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); + isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return ( .documentView-titleWrapper-hover { display: inline-block; } - } - - > .documentView-styleWrapper { + > .documentView-contentsView { + opacity: 0.5; + } > .documentView-captionWrapper { opacity: 1; } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 9e3b42cf6..b495a9399 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -93,7 +93,6 @@ export class PresBox extends ViewBoxBaseComponent() { @observable _expandBoolean: 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; @@ -206,8 +205,7 @@ export class PresBox extends ViewBoxBaseComponent() { targetDoc._viewTransition = 'all 1s'; setTimeout(() => (targetDoc._viewTransition = undefined), 1010); this.nextKeyframe(targetDoc, activeItem); - if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); - else targetDoc.keyFrameEditing = true; + targetDoc.keyFrameEditing = true; }; _mediaTimer!: [NodeJS.Timeout, Doc]; @@ -246,22 +244,13 @@ export class PresBox extends ViewBoxBaseComponent() { // Called when the user activates 'next' - to move to the next part of the pres. trail @action next = () => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - const lastFrame = Cast(targetDoc?.lastFrame, 'number', null); - const curFrame = NumCast(targetDoc?._currentFrame); - let internalFrames: boolean = false; - if (activeItem.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true; - if (internalFrames && lastFrame !== undefined && curFrame < lastFrame) { - // Case 1: There are still other frames and should go through all frames before going to next slide - this.nextInternalFrame(targetDoc, activeItem); - } else if (this.childDocs[this.itemIndex + 1] !== undefined) { - // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide + if (this.childDocs[this.itemIndex + 1] !== undefined) { + // Case 1: No more frames in current doc and next slide is defined, therefore move to next slide const slides = DocListCast(this.rootDoc[StrCast(this.presFieldKey, 'data')]); const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex; this.nextSlide(curLast + 1); } else if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) { - // Case 3: Last slide and presLoop is toggled ON or it is in Edit mode + // Case 2: Last slide and presLoop is toggled ON or it is in Edit mode this.nextSlide(0); } return this.itemIndex; @@ -551,51 +540,6 @@ export class PresBox extends ViewBoxBaseComponent() { } } - /** - * Uses the viewfinder to progressivize through the different views of a single collection. - * @param activeItem: document for which internal zoom is used - */ - zoomProgressivizeNext = (activeItem: Doc) => { - const targetDoc: Doc = this.targetDoc; - const srcContext = Cast(targetDoc?.context, Doc, null); - const docView = DocumentManager.Instance.getDocumentView(targetDoc); - const vfLeft = this.checkList(targetDoc, activeItem['viewfinder-left-indexed']); - const vfWidth = this.checkList(targetDoc, activeItem['viewfinder-width-indexed']); - const vfTop = this.checkList(targetDoc, activeItem['viewfinder-top-indexed']); - const vfHeight = this.checkList(targetDoc, activeItem['viewfinder-height-indexed']); - // Case 1: document that is not a Golden Layout tab - if (srcContext) { - const srcDocView = DocumentManager.Instance.getDocumentView(srcContext); - if (srcDocView) { - const layoutdoc = Doc.Layout(targetDoc); - const panelWidth: number = srcDocView.props.PanelWidth(); - const panelHeight: number = srcDocView.props.PanelHeight(); - const newPanX = NumCast(targetDoc.x) + NumCast(layoutdoc._width) / 2; - const newPanY = NumCast(targetDoc.y) + NumCast(layoutdoc._height) / 2; - const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); - srcContext._panX = newPanX + (vfLeft + vfWidth / 2); - srcContext._panY = newPanY + (vfTop + vfHeight / 2); - srcContext._viewScale = newScale; - } - } - // Case 2: document is the containing collection - if (docView && !srcContext) { - const panelWidth: number = docView.props.PanelWidth(); - const panelHeight: number = docView.props.PanelHeight(); - const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); - targetDoc._panX = vfLeft + vfWidth / 2; - targetDoc._panY = vfTop + vfWidth / 2; - targetDoc._viewScale = newScale; - } - const resize = document.getElementById('resizable'); - if (resize) { - resize.style.width = vfWidth + 'px'; - resize.style.height = vfHeight + 'px'; - resize.style.top = vfTop + 'px'; - resize.style.left = vfLeft + 'px'; - } - }; - /** * For 'Hide Before' and 'Hide After' buttons making sure that * they are hidden each time the presentation is updated. @@ -643,6 +587,7 @@ export class PresBox extends ViewBoxBaseComponent() { //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc @action startAutoPres = async (startSlide: number) => { + if (!this.childDocs.length) return; this.layoutDoc.presStatus = PresStatus.Autoplay; this.startPresentation(startSlide + 1 === this.childDocs.length ? 0 : startSlide); clearTimeout(this._presTimer); @@ -682,14 +627,9 @@ export class PresBox extends ViewBoxBaseComponent() { }; // The function allows for viewing the pres path on toggle - @action togglePath = (srcContext: Doc, off?: boolean) => { - if (off) { - this._pathBoolean = false; - srcContext.presPathView = false; - } else { - runInAction(() => (this._pathBoolean = !this._pathBoolean)); - srcContext.presPathView = this._pathBoolean; - } + @action togglePath = (off?: boolean) => { + this._pathBoolean = off ? false : !this._pathBoolean; + CollectionFreeFormView.ShowPresPaths = this._pathBoolean; }; // The function allows for expanding the view of pres on toggle @@ -1014,17 +954,6 @@ export class PresBox extends ViewBoxBaseComponent() { } }; - /** - * - */ - @action - viewPaths = () => { - const srcContext = Cast(this.rootDoc.presCollection, Doc, null); - if (srcContext) { - this.togglePath(srcContext); - } - }; - 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 @@ -1104,16 +1033,14 @@ export class PresBox extends ViewBoxBaseComponent() { */ @computed get paths() { let pathPoints = ''; - const presCollection = Cast(this.rootDoc.presCollection, Doc, null); this.childDocs.forEach((doc, index) => { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); - const srcContext = Cast(tagDoc?.context, Doc, null); - if (tagDoc && presCollection === srcContext) { + if (tagDoc) { const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2; const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2; if ((index = 0)) pathPoints = n1x + ',' + n1y; else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; - } else if (doc.presPinView && presCollection === tagDoc) { + } else if (doc.presPinView) { const n1x = NumCast(doc.presPinViewX); const n1y = NumCast(doc.presPinViewY); if ((index = 0)) pathPoints = n1x + ',' + n1y; @@ -1137,6 +1064,7 @@ export class PresBox extends ViewBoxBaseComponent() { /> ); } + getPaths = (collection: Doc) => this.paths; // needs to be smarter and figure out the paths to draw for this specific collection. or better yet, draw everything in an overlay layer instad of within a collection // Converts seconds to ms and updates presTransition setTransitionTime = (number: String, change?: number) => { @@ -1840,7 +1768,6 @@ export class PresBox extends ViewBoxBaseComponent() { // CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } - // if (tagDoc.editScrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); @@ -1882,123 +1809,6 @@ export class PresBox extends ViewBoxBaseComponent() { @observable private openActiveColorPicker: boolean = false; @observable private openViewedColorPicker: boolean = false; - @computed get progressivizeDropdown() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - if (activeItem && targetDoc) { - 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; - })}>
-
- {this.activeColorPicker} -
-
Viewed font color
-
(this.openViewedColorPicker = !this.openViewedColorPicker))}>
-
- {this.viewedColorPicker} -
-
- Zoom -
-
- Edit -
-
-
-
- Scroll -
-
- 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)}
-
-
-
- {this.frameListHeader} - {this.frameList} -
-
console.log(' TODO: play frames')}> - Play -
-
-
- ); - } - } - @undoBatch @action switchActive = (color: ColorState) => { @@ -2033,262 +1843,7 @@ export class PresBox extends ViewBoxBaseComponent() { } @action - turnOffEdit = (paths?: boolean) => { - // Turn off paths - if (paths) { - const srcContext = Cast(this.rootDoc.presCollection, Doc, null); - if (srcContext) this.togglePath(srcContext, true); - } - // Turn off the progressivize editors for each document - this.childDocs.forEach(doc => { - doc.editSnapZoomProgressivize = false; - doc.editZoomProgressivize = false; - const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); - if (targetDoc) { - targetDoc.editZoomProgressivize = false; - // targetDoc.editScrollProgressivize = false; - } - }); - }; - - //Toggle whether the user edits or not - @action - editZoomProgressivize = (e: React.MouseEvent) => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - if (!targetDoc.editZoomProgressivize) { - if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; - targetDoc.zoomProgressivize = true; - targetDoc.editZoomProgressivize = true; - activeItem.editZoomProgressivize = true; - } else { - targetDoc.editZoomProgressivize = false; - activeItem.editZoomProgressivize = false; - } - }; - - //Toggle whether the user edits or not - @action - editScrollProgressivize = (e: React.MouseEvent) => { - const targetDoc: Doc = this.targetDoc; - if (!targetDoc.editScrollProgressivize) { - if (!targetDoc.scrollProgressivize) { - targetDoc.scrollProgressivize = true; - this.activeItem.scrollProgressivize = true; - } - targetDoc.editScrollProgressivize = true; - } else { - targetDoc.editScrollProgressivize = false; - } - }; - - //Progressivize Zoom - @action - progressivizeScroll = (e: React.MouseEvent) => { - e.stopPropagation(); - this.activeItem.scrollProgressivize = !this.activeItem.scrollProgressivize; - const targetDoc: Doc = this.targetDoc; - targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize; - // CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); - if (targetDoc.editScrollProgressivize) { - targetDoc.editScrollProgressivize = false; - targetDoc._currentFrame = 0; - targetDoc.lastFrame = 0; - } - }; - - //Progressivize Zoom - @action - progressivizeZoom = (e: React.MouseEvent) => { - e.stopPropagation(); - const activeItem: Doc = this.activeItem; - activeItem.zoomProgressivize = !activeItem.zoomProgressivize; - const targetDoc: Doc = this.targetDoc; - targetDoc.zoomProgressivize = !targetDoc.zoomProgressivize; - CollectionFreeFormDocumentView.setupZoom(activeItem, targetDoc); - if (activeItem.editZoomProgressivize) { - activeItem.editZoomProgressivize = false; - targetDoc._currentFrame = 0; - targetDoc.lastFrame = 0; - } - }; - - //Progressivize Child Docs - @action - editProgressivize = (e: React.MouseEvent) => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - targetDoc._currentFrame = targetDoc.lastFrame; - if (!targetDoc.editProgressivize) { - if (!activeItem.presProgressivize) { - activeItem.presProgressivize = true; - targetDoc.presProgressivize = true; - } - targetDoc.editProgressivize = true; - } else { - targetDoc.editProgressivize = false; - } - }; - - @action - progressivizeChild = (e: React.MouseEvent) => { - e.stopPropagation(); - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - if (!activeItem.presProgressivize) { - targetDoc.keyFrameEditing = false; - activeItem.presProgressivize = true; - targetDoc.presProgressivize = true; - targetDoc._currentFrame = 0; - docs.forEach((doc, i) => CollectionFreeFormDocumentView.setupKeyframes([doc], i, true)); - targetDoc.lastFrame = targetDoc.lastFrame ? NumCast(targetDoc.lastFrame) : docs.length - 1; - } else { - // targetDoc.editProgressivize = false; - activeItem.presProgressivize = false; - targetDoc.presProgressivize = false; - targetDoc._currentFrame = 0; - targetDoc.keyFrameEditing = true; - } - }; - - @action - checkMovementLists = (doc: Doc, xlist: any, ylist: any) => { - const x: List = xlist; - const y: List = ylist; - const tags: JSX.Element[] = []; - let pathPoints = ''; //List of all of the pathpoints that need to be added - for (let i = 0; i < x.length - 1; i++) { - if (y[i] || x[i]) { - if (i === 0) pathPoints = x[i] - 11 + ',' + (y[i] + 33); - else pathPoints = pathPoints + ' ' + (x[i] - 11) + ',' + (y[i] + 33); - tags.push( -
- {i} -
- ); - } - } - tags.push( - - - - ); - return tags; - }; - - @observable - toggleDisplayMovement = (doc: Doc) => (doc.displayMovement = !doc.displayMovement); - - @action - checkList = (doc: Doc, list: any): number => { - const x: List = list; - 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)]; - } - return 100; - }; - - @computed get progressivizeChildDocs() { - const targetDoc: Doc = this.targetDoc; - const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - const tags: JSX.Element[] = []; - docs.forEach((doc, index) => { - if (doc['x-indexed'] && doc['y-indexed']) { - tags.push(
{this.checkMovementLists(doc, doc['x-indexed'], doc['y-indexed'])}
); - } - tags.push( -
{ - if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; - }} - onPointerOver={() => { - if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; - }} - onClick={e => { - this.toggleDisplayMovement(doc); - e.stopPropagation(); - }} - style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : '#c8c8c8', top: NumCast(doc.y), left: NumCast(doc.x) }}> -
- { - e.stopPropagation(); - this.prevAppearFrame(doc, index); - }} - /> -
-
{NumCast(doc.appearFrame)}
-
- { - e.stopPropagation(); - this.nextAppearFrame(doc, index); - }} - /> -
-
- ); - }); - return tags; - } - - @action - 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) => { - doc.appearFrame = Math.max(0, (Cast(doc.appearFrame, 'number', null) ?? 0) - 1); - this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); - }; - - @action - updateOpacityList = (list: any, frame: number) => { - const x: List = list; - if (x && x.length >= frame) { - for (let i = 0; i < x.length; i++) { - if (i < frame) { - x[i] = 0; - } else if (i >= frame) { - x[i] = 1; - } - } - list = x; - } else { - x.length = frame + 1; - for (let i = 0; i < x.length; i++) { - if (i < frame) { - x[i] = 0; - } else if (i >= frame) { - x[i] = 1; - } - } - list = x; - } - }; + turnOffEdit = (paths?: boolean) => paths && this.togglePath(true); // Turn off paths @computed get toolbarWidth(): number { @@ -2311,11 +1866,11 @@ 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 }} + style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} className={'toolbar-button'} - onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}> + onClick={this.childDocs.length > 1 ? () => this.togglePath() : undefined}>
-- cgit v1.2.3-70-g09d2 From 19f317bd43a7cc8df0daf1c0642011cc8754e14b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 15 Nov 2022 12:49:53 -0500 Subject: made InPlace container tool button apply more settings to be more like a template. added followAllLInks flag. added image anchors to save pan zoom. added follow link button bar option for follow all links. added hideDecorations flag and property --- src/client/documents/Documents.ts | 5 ++ src/client/util/LinkFollower.ts | 11 ++-- src/client/views/DocumentButtonBar.scss | 31 ++++++++++- src/client/views/DocumentButtonBar.tsx | 60 +++++++++++++++------- src/client/views/DocumentDecorations.tsx | 19 +++---- src/client/views/PropertiesButtons.tsx | 17 ++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/linking/LinkEditor.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 3 +- src/client/views/nodes/ImageBox.tsx | 52 ++++++++++++++++--- src/client/views/nodes/trails/PresBox.tsx | 3 +- 11 files changed, 154 insertions(+), 50 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e44004f45..8f45802fe 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -257,6 +257,7 @@ export class DocumentOptions { lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection appearFrame?: number; // the frame in which the document appears + viewTransitionTime?: number; // transition duration for view parameters presTransition?: number; //the time taken for the transition TO a document presDuration?: number; //the duration of the slide in presentation view borderRounding?: string; @@ -1047,6 +1048,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id); } + export function ImageanchorDocument(options: DocumentOptions = {}, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id); + } + export function HTMLAnchorDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), options, id); } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index c09c9d1c5..c6fc7b372 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,6 +1,7 @@ import { action, runInAction } from 'mobx'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { BoolCast, Cast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, DocCast, StrCast } from '../../fields/Types'; +import { DocumentType } from '../documents/DocumentTypes'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; @@ -76,11 +77,11 @@ export class LinkFollower { const linkDocs = link ? [link] : DocListCast(sourceDoc.links); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 - const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); - const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); + const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor2 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor2 as Doc).annotationOn) : (d.anchor2 as Doc)).length === 0); + const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor1 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor1 as Doc).annotationOn) : (d.anchor1 as Doc)).length === 0); const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; - const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs; - const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1); + const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs; + const followLinks = sourceDoc.isPushpin || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1); var count = 0; const allFinished = () => ++count === followLinks.length && finished?.(); followLinks.forEach(async linkDoc => { diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index 1e93ba5e2..f9c988fdd 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -18,15 +18,44 @@ $linkGap: 3px; cursor: pointer; } +.documentButtonBar-followTypes, .documentButtonBar-pinTypes { position: absolute; - display: flex; + display: none; width: 60px; top: -14px; background: black; height: 20px; align-items: center; } +.documentButtonBar-followTypes { + width: 20px; + display: none; +} +.documentButtonBar-followIcon { + align-items: center; + display: flex; + height: 100%; + &:hover { + background-color: lightblue; + } +} +.documentButtonBar-follow { + &:hover { + .documentButtonBar-followTypes { + display: flex; + } + } +} +.documentButtonBar-pin { + color: white; + &:hover { + .documentButtonBar-pinTypes { + display: flex; + } + } +} + .documentButtonBar-pinIcon { &:hover { background-color: lightblue; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 36875290e..794b51cc5 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 { BoolCast, Cast, NumCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; @@ -209,21 +209,52 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); } + @observable subFollow = ''; @computed get followLinkButton() { const targetDoc = this.view0?.props.Document; + const followBtn = (allDocs: boolean, click: (doc: Doc) => void, isSet: (doc?: Doc) => boolean, icon: IconProp) => { + const tooltip = `Follow ${this.subPin}documents`; + return !tooltip ? null : ( + {tooltip}
}> +
+ (this.subPin = allDocs ? 'All ' : ''))} + onPointerLeave={action(e => (this.subPin = ''))} + onClick={e => { + this.props.views().forEach(dv => click(dv!.rootDoc)); + e.stopPropagation(); + }} + /> +
+ + ); + }; return !targetDoc ? null : ( - {'Set onClick to follow primary link'}
}> + Set onClick to follow primary link
}>
this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}> +
+ {followBtn( + true, + (doc: Doc) => (doc.followAllLinks = !doc.followAllLinks), + (doc?: Doc) => (doc?.followAllLinks ? true : false), + 'window-maximize' + )} +
); } - @observable expandPin = false; + @observable subPin = ''; @computed get pinButton() { @@ -264,10 +295,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !targetDoc ? null : ( {`Pin Document ${SelectionManager.Views().length > 1 ? 'multiple documents' : ''} to Trail`}
}>
(this.expandPin = true))} - onPointerLeave={action(e => (this.expandPin = false))} + className="documentButtonBar-icon documentButtonBar-pin" onClick={e => { const docs = this.props .views() @@ -276,13 +304,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}> - {this.expandPin ? ( -
- {pinBtn(true, false, 'window-maximize')} - {pinBtn(false, true, 'address-card')} - {pinBtn(true, true, 'id-card')} -
- ) : null} +
+ {pinBtn(true, false, 'window-maximize')} + {pinBtn(false, true, 'address-card')} + {pinBtn(true, true, 'id-card')} +
@@ -460,9 +486,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ) : null} { Doc.noviceMode ? null :
{this.templateButton}
- /*
- {this.metadataButton} -
*/ + /*
{this.metadataButton}
*/ } {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3fac137fe..3efb5fb37 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -708,24 +708,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } render() { - const { b, c, r, x, y } = this.Bounds; - const bounds = { b, c, r, x, y }; + const { b, r, x, y } = this.Bounds; + const bounds = { b, r, x, y }; const seldocview = SelectionManager.Views().slice(-1)[0]; if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return null; } // hide the decorations if the parent chooses to hide it or if the document itself hides it - const hideResizers = seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating; - const hideTitle = seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; - const hideDocumentButtonBar = seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating; + const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations; + const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating; + const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; + const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button - const hideOpenButton = + const hideOpenButton =hideDecorations || seldocview.props.hideOpenButton || seldocview.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) || this._isRounding || this._isRotating; - const hideDeleteButton = + const hideDeleteButton =hideDecorations || this._isRounding || this._isRotating || seldocview.props.hideDeleteButton || @@ -767,7 +768,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); const useLock = bounds.r - bounds.x > 135 && seldocview.props.CollectionFreeFormDocumentView; - const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate? + const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate? const rotation = NumCast(seldocview.rootDoc._rotation); const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; @@ -837,7 +838,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
{hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} - {titleArea} + {hideTitle ? null : titleArea} {hideOpenButton ?
: topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
{hideResizers ? null : ( diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 5992b3eb9..d27848a90 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -121,15 +121,24 @@ export class PropertiesButtons extends React.Component<{}, {}> { onClick => { SelectionManager.Views().forEach(dv => { const containerDoc = dv.rootDoc; - containerDoc.noShadow = containerDoc.noHighlighting = containerDoc._isLinkButton = containerDoc._fitContentsToBox = containerDoc._forceActive = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer; + containerDoc.followAllLinks = + containerDoc.noShadow = + containerDoc.noHighlighting = + containerDoc._isLinkButton = + containerDoc._fitContentsToBox = + containerDoc._forceActive = + containerDoc._isInPlaceContainer = + !containerDoc._isInPlaceContainer; containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? 'inPlace' : undefined; containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined; const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement(); if (menuDoc) { - menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer; - DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container'); + menuDoc.hideDecorations = menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer; + if (!dv.allLinks.find(link => link.anchor1 === menuDoc || link.anchor2 === menuDoc)) { + DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container'); + } DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => { - menuItem._isLinkButton = true; + menuItem.followAllLinks = menuItem._isLinkButton = true; menuItem._followLinkLocation = 'inPlace'; }); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e24b116d0..8fe5ad63f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1157,7 +1157,7 @@ export class CollectionFreeFormView extends CollectionSubView { @computed get editAudioFollow() { //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - console.log('AudioFollow:' + this.audioFollow); return (
Play Target Audio:
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7accd49a4..76d6d3532 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -166,7 +166,8 @@ export interface DocumentViewSharedProps { // these props are specific to DocuentViews export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView - hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected + hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected + hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDocumentButtonBar?: boolean; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d0df41023..d67481b22 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -14,7 +14,7 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; -import { DocUtils } from '../../documents/Documents'; +import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; @@ -49,21 +49,58 @@ export class ImageBox extends ViewBoxAnnotatableComponent) => Opt = () => undefined; @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; + @observable _focusViewScale: number | undefined = 1; + @observable _focusPanX: number | undefined = 0; + @observable _focusPanY: number | undefined = 0; + get viewScale() { + return this._focusViewScale || StrCast(this.layoutDoc._viewScale); + } + get panX() { + return this._focusPanX || StrCast(this.layoutDoc._panX); + } + get panY() { + return this._focusPanY || StrCast(this.layoutDoc._panY); + } protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); }; - setViewSpec = (anchor: Doc, preview: boolean) => {}; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document + + @action + setViewSpec = (anchor: Doc, preview: boolean) => { + if (preview && anchor._viewScale !== undefined) { + this._focusViewScale = Cast(anchor._viewScale, 'number', null); + this._focusPanX = Cast(anchor._panX, 'number', null); + this._focusPanY = Cast(anchor._panX, 'number', null); + } else if (anchor._viewScale !== undefined) { + const smoothTime = NumCast(anchor.viewTransitionTime); + this.layoutDoc.viewTransition = `all ${smoothTime}ms`; + this.layoutDoc._panX = NumCast(anchor._panX, NumCast(this.layoutDoc._panY)); + this.layoutDoc._panY = NumCast(anchor._panY, NumCast(this.layoutDoc._panX)); + this.layoutDoc._viewScale = NumCast(anchor._viewScale, NumCast(this.layoutDoc._viewScale)); + if (anchor.type === DocumentType.MARKER) { + this.dataDoc[this.annotationKey] = new List(DocListCast(anchor._annotations)); + } + clearTimeout(this._transitioning); + this._transitioning = setTimeout(() => (this.layoutDoc.viewTransition = undefined), smoothTime); + } + }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document getAnchor = () => { - const anchor = this._getAnchor?.(this._savedAnnotations); - anchor && this.addDocument(anchor); - return anchor ?? this.rootDoc; + const zoomedAnchor = () => Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, _viewScale: NumCast(this.layoutDoc._viewScale), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) }); + const anchor = this._getAnchor?.(this._savedAnnotations) ?? (this.layoutDoc.viewScale ? zoomedAnchor() : undefined); + if (anchor) { + anchor._annotations = new List(DocListCast(this.dataDoc[this.annotationKey])); + this.addDocument(anchor); + return anchor; + } + return this.rootDoc; }; componentDidMount() { @@ -90,6 +127,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent disposer?.()); } @@ -283,9 +321,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { this._uploadIcon = idle; - if (data) { - dataDoc[this.fieldKey] = data; - } + data && (dataDoc[this.fieldKey] = data); }), 2000 ); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index b495a9399..8d805c663 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -509,8 +509,7 @@ export class PresBox extends ViewBoxBaseComponent() { static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) { if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) { const transTime = NumCast(activeItem.presTransition, 500); - const presTransitionTime = `all ${transTime}ms`; - targetDoc._dataTransition = presTransitionTime; + targetDoc._dataTransition = `all ${transTime}ms`; targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x)); targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y)); targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation)); -- cgit v1.2.3-70-g09d2 From ae324ff50865929be836edf3bbf129207638a9c9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 16 Nov 2022 16:26:31 -0500 Subject: big changes to make link following use the same code as pinning docs for trails. --- src/client/documents/Documents.ts | 1 + src/client/util/DocumentManager.ts | 30 ++--- src/client/util/LinkFollower.ts | 7 +- src/client/views/DocumentButtonBar.tsx | 6 +- src/client/views/GlobalKeyHandler.ts | 4 +- src/client/views/MarqueeAnnotator.tsx | 1 + src/client/views/StyleProvider.tsx | 2 +- .../collections/CollectionStackedTimeline.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 ++- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/ImageBox.tsx | 60 ++++------ src/client/views/nodes/PDFBox.tsx | 16 ++- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 131 +++++++++++++-------- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 11 +- 18 files changed, 178 insertions(+), 129 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8f45802fe..e68b9e27b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -237,6 +237,7 @@ export class DocumentOptions { childContextMenuScripts?: List; childContextMenuLabels?: List; childContextMenuIcons?: List; + followLinkZoom?: boolean; // whether to zoom to the target of a link hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideDecorationTitle?: boolean; hideOpenButton?: boolean; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 9336717c0..a60c1ed6b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -151,6 +151,21 @@ export class DocumentManager { return toReturn; } + static playAudioAnno(doc: Doc) { + const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement(); + if (anno) { + if (anno instanceof AudioField) { + new Howl({ + src: [anno.url.href], + format: ['mp3'], + autoplay: true, + loop: false, + volume: 0.5, + }); + } + } + } + static addView = (doc: Doc, finished?: () => void) => { CollectionDockingView.AddSplit(doc, 'right'); finished?.(); @@ -189,20 +204,6 @@ export class DocumentManager { } else { finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); !noSelect && docView?.select(false); - if (originatingDoc?.followLinkAudio) { - const anno = Cast(finalTargetDoc[Doc.LayoutFieldKey(finalTargetDoc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement(); - if (anno) { - if (anno instanceof AudioField) { - new Howl({ - src: [anno.url.href], - format: ['mp3'], - autoplay: true, - loop: false, - volume: 0.5, - }); - } - } - } } finished?.(); }; @@ -233,6 +234,7 @@ export class DocumentManager { } if (focusView) { !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox + if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc); const doFocus = (forceDidFocus: boolean) => focusView.focus(originalTarget ?? targetDoc, { originalTarget, diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index c6fc7b372..68716a207 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -6,6 +6,7 @@ import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; import { DocumentManager } from './DocumentManager'; +import { LinkManager } from './LinkManager'; import { UndoManager } from './UndoManager'; type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; @@ -25,7 +26,7 @@ export class LinkFollower { // follows a link - if the target is on screen, it highlights/pans to it. // if the target isn't onscreen, then it will open up the target in the lightbox, or in place // depending on the followLinkLocation property of the source (or the link itself as a fallback); - public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => { + public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => { const batch = UndoManager.StartBatch('follow link click'); // open up target if it's not already in view ... const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { @@ -63,7 +64,6 @@ export class LinkFollower { linkDoc, sourceDoc, createViewFunc, - BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, action(() => { batch.end(); @@ -73,7 +73,7 @@ export class LinkFollower { ); }; - public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { + public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { const linkDocs = link ? [link] : DocListCast(sourceDoc.links); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 @@ -96,6 +96,7 @@ export class LinkFollower { : linkDoc.anchor1 ) as Doc; if (target) { + const zoom = BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false); if (target.TourMap) { const fieldKey = Doc.LayoutFieldKey(target); const tour = DocListCast(target[fieldKey]).reverse(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 794b51cc5..681349ccf 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -284,7 +284,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }} /> @@ -301,7 +301,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}>
@@ -488,7 +488,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV Doc.noviceMode ? null :
{this.templateButton}
/*
{this.metadataButton}
*/ } - {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
} + {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{this.recordButton}
{!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null :
{this.shareButton}
} diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 5a6caf995..4890d9624 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -6,7 +6,7 @@ import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; -import { Cast, PromiseValue } from '../../fields/Types'; +import { Cast, DocCast, PromiseValue } from '../../fields/Types'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { DocumentType } from '../documents/DocumentTypes'; @@ -245,7 +245,7 @@ export class KeyManager { if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) { SelectionManager.Views()[0].ComponentView?.search?.('', false, false); } else { - const searchBtn = Doc.MySearcher; + const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher); if (searchBtn) { MainView.Instance.selectMenu(searchBtn); } diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index c0dd62a05..1162cde50 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -93,6 +93,7 @@ export class MarqueeAnnotator extends React.Component { dragComplete: e => { if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; + e.annoDragData.linkSourceDoc.followLinkZoom = false; } }, }); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index a04f4a4f4..8ee5ebdb6 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -236,7 +236,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (darkScheme() ? Colors.BLACK : 'linear-gradient(#065fff, #85c1f9)')); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 7bf798656..39ae470b6 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -748,7 +748,7 @@ class StackedTimelineAnchor extends React.Component time < NumCast(this.props.mark[this.props.endTag]) && this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5 ) { - LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true); + LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false); } this._lastTimecode = time; } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 1a9006356..e21649648 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -229,7 +229,7 @@ export class TabDocView extends React.Component { * Adds a document to the presentation view **/ @action - public static PinDoc(docs: Doc | Doc[], pinProps?: PinProps) { + public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) { const docList = docs instanceof Doc ? [docs] : docs; const batch = UndoManager.StartBatch('pinning doc'); @@ -271,7 +271,7 @@ export class TabDocView extends React.Component { pinDoc.presStartTime = NumCast(doc.clipStart); pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } - PresBox.pinDocView(pinDoc, pinProps, doc); + PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, doc); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); //save position diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 932bd789d..8a97797c7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1530,11 +1530,27 @@ export class CollectionFreeFormView extends CollectionSubView { + let focusSpeed: Opt; + PresBox.restoreTargetDocView( + this.rootDoc, // + { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + anchor, + (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)), + { + pannable: anchor.presPinData ? true : false, + } + ); + return focusSpeed; + }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document + getAnchor = () => { if (this.props.Document.annotationOn) { return this.rootDoc; } - const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); + const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc }); + PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc); const proto = Doc.GetProto(anchor); proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List([]); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index d74da9748..dd03b9b99 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -103,7 +103,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { - whichDoc !== targetDoc && r?.focus(whichDoc, {}); + whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true }); }} {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit} isContentActive={returnFalse} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 84cacd919..dc468cf89 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -51,7 +51,7 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { LinkDocPreview } from './LinkDocPreview'; import { RadialMenu } from './RadialMenu'; import { ScriptingBox } from './ScriptingBox'; -import { PresBox } from './trails/PresBox'; +import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); const { Howl } = require('howler'); @@ -144,7 +144,7 @@ export interface DocumentViewSharedProps { addDocument?: (doc: Doc | Doc[]) => boolean; removeDocument?: (doc: Doc | Doc[]) => boolean; moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; - pinToPres: (document: Doc) => void; + pinToPres: (document: Doc, pinProps: PinProps) => void; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; canEmbedOnDrag?: boolean; @@ -486,7 +486,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 }); - RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document), icon: 'map-pin', selected: -1 }); + RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document, {}), icon: 'map-pin', selected: -1 }); RadialMenu.Instance.addItem({ description: 'Open', event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: 'trash', selected: -1 }); SelectionManager.DeselectAll(); @@ -545,7 +545,7 @@ export class DocumentViewInternal extends DocComponent (this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec]))); // after a render the general viewSpec should have created the right _componentView, so after a timeout, call the componentview to update its specific view specs setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); - const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); + const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant !== true && !LinkDocPreview.LinkInfo); const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 76ba7765c..2e594d96a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -30,6 +30,8 @@ import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; import React = require('react'); +import { PresBox } from './trails'; +import { DocumentViewProps } from './DocumentView'; export const pageSchema = createSchema({ googlePhotosUrl: 'string', @@ -49,23 +51,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent) => Opt = () => undefined; @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; - @observable _focusViewScale: number | undefined = 1; - @observable _focusPanX: number | undefined = 0; - @observable _focusPanY: number | undefined = 0; - get viewScale() { - return this._focusViewScale || StrCast(this.layoutDoc._viewScale); - } - get panX() { - return this._focusPanX || StrCast(this.layoutDoc._panX); - } - get panY() { - return this._focusPanY || StrCast(this.layoutDoc._panY); - } protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); @@ -73,33 +62,30 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - if (preview && anchor._viewScale !== undefined) { - this._focusViewScale = Cast(anchor._viewScale, 'number', null); - this._focusPanX = Cast(anchor._panX, 'number', null); - this._focusPanY = Cast(anchor._panX, 'number', null); - } else if (anchor._viewScale !== undefined) { - const smoothTime = NumCast(anchor.viewTransitionTime); - this.layoutDoc.viewTransition = `all ${smoothTime}ms`; - this.layoutDoc._panX = NumCast(anchor._panX, NumCast(this.layoutDoc._panY)); - this.layoutDoc._panY = NumCast(anchor._panY, NumCast(this.layoutDoc._panX)); - this.layoutDoc._viewScale = NumCast(anchor._viewScale, NumCast(this.layoutDoc._viewScale)); - this.layoutDoc[this.fieldKey + '-useAlt'] = Cast(anchor._useAlt, 'boolean', null); - if (anchor.type === DocumentType.MARKER) { - this.dataDoc[this.annotationKey] = new List(DocListCast(anchor._annotations)); - } - clearTimeout(this._transitioning); - this._transitioning = setTimeout(() => (this.layoutDoc.viewTransition = undefined), smoothTime); - } + scrollFocus = (anchor: Doc, smooth: boolean) => { + let focusSpeed: Opt; + PresBox.restoreTargetDocView( + this.rootDoc, // + { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + anchor, + (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)), + !anchor.presPinData + ? {} + : { + pannable: true, + dataannos: anchor.presAnnotations !== undefined, + dataview: true, + } + ); + return focusSpeed; }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document getAnchor = () => { const anchor = this._getAnchor?.(this._savedAnnotations) ?? // use marquee anchor, otherwise, save zoom/pan as anchor - Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, unrendered: true, annotationOn: this.rootDoc, _viewScale: NumCast(this.layoutDoc._viewScale, 1), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) }); + Docs.Create.ImageanchorDocument({ presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); if (anchor) { - anchor._useAlt = Cast(this.layoutDoc[this.fieldKey + '-useAlt'], 'boolean', null); - anchor._annotations = new List(DocListCast(this.dataDoc[this.annotationKey])); + PresBox.pinDocView(anchor, { pinData: { pannable: true, dataview: true, dataannos: true } }, this.rootDoc); this.addDocument(anchor); return anchor; } @@ -130,7 +116,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent disposer?.()); } @@ -425,6 +410,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent this._savedAnnotations; + styleProvider = (doc: Opt, props: Opt, property: string): any => { + if (property === StyleProp.BoxShadow) return undefined; + return this.props.styleProvider?.(doc, props, property); + }; render() { TraceMobx(); @@ -445,6 +434,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @@ -207,16 +208,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - const anchor = - this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? - Docs.Create.TextanchorDocument({ + const docAnchor = () => { + const anchor = Docs.Create.TextanchorDocument({ title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), - y: NumCast(this.layoutDoc._scrollTop), unrendered: true, }); + PresBox.pinDocView(anchor, { pinData: { scrollable: true, pannable: true } }, this.rootDoc); + return anchor; + }; + const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? docAnchor(); this.addDocument(anchor); return anchor; }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2984feba5..fdd61463d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -686,7 +686,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.props.pinToPres(anchor); + pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {}); @undoBatch makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 8d805c663..10f2dc016 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -12,7 +12,8 @@ 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, StopEvent } from '../../../../Utils'; +import { AudioField } from '../../../../fields/URLField'; +import { emptyFunction, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -33,6 +34,7 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +const { Howl } = require('howler'); export interface PinProps { audioRange?: boolean; @@ -41,6 +43,17 @@ export interface PinProps { pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) pinDocLayout?: boolean; // pin layout info (width/height/x/y) pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text) + pinAudioPlay?: boolean; // pin audio annotation + pinData?: { + scrollable?: boolean | undefined; + pannable?: boolean | undefined; + temporal?: boolean | undefined; + clippable?: boolean | undefined; + dataview?: boolean | undefined; + textview?: boolean | undefined; + poslayoutview?: boolean | undefined; + dataannos?: boolean | undefined; + }; } @observer @@ -333,26 +346,37 @@ export class PresBox extends ViewBoxBaseComponent() { this.onHideDocument(); //Handles hide after/before } }); - static pinDataTypes(target: Doc) { + static pinDataTypes(target: Doc): { scrollable?: boolean; pannable?: boolean; temporal?: boolean; clippable?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; dataannos?: boolean } { 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 pannable = [DocumentType.IMG, DocumentType.PDF].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); const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(target.type as any) && target.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined; const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; - return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview }; + const dataannos = false; + return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview, dataannos }; } @action - static restoreTargetDocView(bestTarget: Doc, activeItem: Doc) { - const transTime = NumCast(activeItem.presTransition, 500); + playAnnotation = (anno: AudioField) => {}; + @action + static restoreTargetDocView(bestTarget: Doc, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTarget)) { const presTransitionTime = `all ${transTime}ms`; - const { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview } = this.pinDataTypes(bestTarget); bestTarget._viewTransition = presTransitionTime; - if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; - if (temporal) bestTarget._currentTimecode = activeItem.presStartTime; - if (scrollable) { + if (pinProps?.pinDocLayout) { + const transTime = NumCast(activeItem.presTransition, 500); + bestTarget._dataTransition = `all ${transTime}ms`; + bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x)); + bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y)); + bestTarget.rotation = NumCast(activeItem.presRot, NumCast(bestTarget.rotation)); + bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width)); + bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height)); + setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); + } + if (pinDataTypes.clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; + if (pinDataTypes.temporal) bestTarget._currentTimecode = activeItem.presStartTime; + if (pinDataTypes.scrollable) { bestTarget._scrollTop = activeItem.presPinViewScroll; const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { @@ -360,13 +384,17 @@ export class PresBox extends ViewBoxBaseComponent() { dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }); } } - if (dataview && activeItem.presData !== undefined) { + if (pinDataTypes.dataannos) { + const fkey = Doc.LayoutFieldKey(bestTarget); + Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List(DocListCast(activeItem.presAnnotations)); + } + if (pinDataTypes.dataview && activeItem.presData !== undefined) { const fkey = Doc.LayoutFieldKey(bestTarget); Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; } - if (textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; - if (poslayoutview) { + if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + if (pinDataTypes.poslayoutview) { StrListCast(activeItem.presPinLayoutData) .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) .forEach(data => { @@ -385,7 +413,7 @@ export class PresBox extends ViewBoxBaseComponent() { transTime + 10 ); } - if (pannable) { + if (pinDataTypes.pannable) { const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }; @@ -410,9 +438,8 @@ export class PresBox extends ViewBoxBaseComponent() { /// 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, targetDoc: Doc) { - const { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview } = this.pinDataTypes(pinDoc); - if (pinProps?.pinDocLayout) { + static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) { + if (pinProps.pinDocLayout) { pinDoc.presPinLayout = true; pinDoc.presX = NumCast(targetDoc.x); pinDoc.presY = NumCast(targetDoc.y); @@ -420,33 +447,46 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); } - if (pinProps?.pinDocContent) { - pinDoc.presPinData = scrollable || temporal || pannable || clippable || dataview || textview || poslayoutview || pinProps.activeFrame !== undefined; - if (dataview) { + if (pinProps.pinAudioPlay) pinDoc.followLinkAudio = true; + if (pinProps.pinData) { + pinDoc.presPinData = + pinProps.pinData.scrollable || + pinProps.pinData.temporal || + pinProps.pinData.pannable || + pinProps.pinData.clippable || + pinProps.pinData.dataview || + pinProps.pinData.textview || + pinProps.pinData.poslayoutview || + pinProps?.activeFrame !== undefined; + if (pinProps.pinData.dataview) { const fkey = Doc.LayoutFieldKey(targetDoc); pinDoc.presUseAlt = targetDoc[fkey + '-useAlt']; pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; } - if (textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; - if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; - if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; - if (poslayoutview) pinDoc.presPinLayoutData = new List(DocListCast(pinDoc.presData).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); - if (pannable) { - pinDoc.presPinViewX = NumCast(pinDoc._panX); - pinDoc.presPinViewY = NumCast(pinDoc._panY); - pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + if (pinProps.pinData.dataannos) { + const fkey = Doc.LayoutFieldKey(targetDoc); + pinDoc.presAnnotations = new List(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations'])); + } + if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; + if (pinProps.pinData.scrollable) pinDoc.presPinViewScroll = targetDoc._scrollTop; + if (pinProps.pinData.clippable) pinDoc.presPinClipWidth = targetDoc._clipWidth; + if (pinProps.pinData.poslayoutview) pinDoc.presPinLayoutData = new List(DocListCast(targetDoc.presData).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); + if (pinProps.pinData.pannable) { + pinDoc.presPinViewX = NumCast(targetDoc._panX); + pinDoc.presPinViewY = NumCast(targetDoc._panY); + pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1); } - if (temporal) { - pinDoc.presStartTime = pinDoc._currentTimecode; - const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(pinDoc.presStartTime) + 0.1); - pinDoc.presEndTime = NumCast(pinDoc.clipEnd, duration); + if (pinProps.pinData.temporal) { + pinDoc.presStartTime = targetDoc._currentTimecode; + const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(targetDoc.presStartTime) + 0.1); + pinDoc.presEndTime = NumCast(targetDoc.clipEnd, duration); } } if (pinProps?.pinViewport) { // If pinWithView option set then update scale and x / y props of slide const bounds = pinProps.pinViewport; pinDoc.presPinView = true; - pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1); pinDoc.presPinViewX = bounds.left + bounds.width / 2; pinDoc.presPinViewY = bounds.top + bounds.height / 2; pinDoc.presPinViewBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); @@ -507,35 +547,24 @@ export class PresBox extends ViewBoxBaseComponent() { }; static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) { - if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) { - const transTime = NumCast(activeItem.presTransition, 500); - targetDoc._dataTransition = `all ${transTime}ms`; - targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x)); - targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y)); - targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation)); - 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 && activeItem.presMovement !== PresMovement.None) { LightboxView.SetLightboxDoc(undefined); const zooming = activeItem.presMovement !== PresMovement.Pan; - DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, finished, undefined, true, NumCast(activeItem.presZoom, 1)); + DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, activeItem, finished, undefined, true, NumCast(activeItem.presZoom, 1)); } else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); } // 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.presPinData || activeItem.presPinView) { + const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; + if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { clearTimeout(PresBox._navTimer); // targetDoc may or may not be displayed. this gets the first available document (or alias) view that matches targetDoc const bestTargetView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - const bestTarget = bestTargetView?.props.Document; - if (bestTarget) PresBox._navTimer = PresBox.restoreTargetDocView(bestTarget, activeItem); - activeItem.presPinAudioPlay && bestTargetView?.docView?.playAnnotation(); + if (bestTargetView?.props.Document) PresBox._navTimer = PresBox.restoreTargetDocView(bestTargetView?.props.Document, { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500)); } } @@ -747,7 +776,7 @@ export class PresBox extends ViewBoxBaseComponent() { } else { if (!doc.aliasOf) { const original = Doc.MakeAlias(doc); - TabDocView.PinDoc(original); + TabDocView.PinDoc(original, {}); setTimeout(() => this.removeDocument(doc), 0); return false; } else { @@ -1316,7 +1345,7 @@ export class PresBox extends ViewBoxBaseComponent() { Effects
Play Audio Annotation
- (activeItem.presPinAudioPlay = !BoolCast(activeItem.presPinAudioPlay))} checked={BoolCast(activeItem.presPinAudioPlay)} /> + (activeItem.followLinkAudio = !BoolCast(activeItem.followLinkAudio))} checked={BoolCast(activeItem.followLinkAudio)} />
() { const presData = Cast(this.rootDoc.data, listSpec(Doc)); if (data && presData) { data.push(doc); - TabDocView.PinDoc(doc); + TabDocView.PinDoc(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); } else { this.props.addDocTab(doc, 'add:right'); diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 9af0949eb..133b882f6 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -52,7 +52,7 @@ class RegionAnnotation extends React.Component { }; @undoBatch - pinToPres = () => this.props.pinToPres(this.annoTextRegion); + pinToPres = () => this.props.pinToPres(this.annoTextRegion, {}); @undoBatch makePushpin = () => (this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6ff87ef9f..5a5c63c3d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -166,12 +166,12 @@ export class PDFViewer extends React.Component { // scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location, // otherwise it will scroll smoothly. - scrollFocus = (doc: Doc, smooth: boolean) => { + scrollFocus = (doc: Doc, scrollTop: number, smooth: boolean) => { const mainCont = this._mainCont.current; let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { 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)); + const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { if (!this._pdfViewer) this._initialScroll = scrollTo; else if (smooth) smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), mainCont, scrollTo); @@ -203,6 +203,11 @@ export class PDFViewer extends React.Component { } document.removeEventListener('pagesinit', this.pagesinit); var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : ''; + this._disposers.scale = reaction( + () => NumCast(this.props.layoutDoc._viewScale, 1), + scale => (this._pdfViewer.currentScaleValue = scale), + { fireImmediately: true } + ); this._disposers.scroll = reaction( () => Math.abs(NumCast(this.props.Document._scrollTop)), pos => { @@ -290,7 +295,7 @@ export class PDFViewer extends React.Component { @action scrollToAnnotation = (scrollToAnnotation: Doc) => { if (scrollToAnnotation) { - this.scrollFocus(scrollToAnnotation, true); + this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), true); Doc.linkFollowHighlight(scrollToAnnotation); } }; -- cgit v1.2.3-70-g09d2 From 4a0686ca3db91cf6e2c984c53f1ad4f2fea8de8b Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Nov 2022 16:28:01 -0500 Subject: more cleanup to addDocTab. fixed pile views to show proper icons instead of loading document label icons --- src/client/documents/Documents.ts | 14 ++++---- src/client/views/MainView.tsx | 11 +++---- .../views/collections/CollectionStackingView.tsx | 10 +----- src/client/views/collections/TabDocView.tsx | 37 ++++++++-------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 28 +++++++++------- .../CollectionMultirowView.tsx | 9 +----- 6 files changed, 45 insertions(+), 64 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e68b9e27b..c5e08eeea 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -37,7 +37,7 @@ import { FontIconBox } from '../views/nodes/button/FontIconBox'; import { ColorBox } from '../views/nodes/ColorBox'; import { ComparisonBox } from '../views/nodes/ComparisonBox'; import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox'; -import { DocFocusOptions } from '../views/nodes/DocumentView'; +import { DocFocusOptions, OpenWhereMod } from '../views/nodes/DocumentView'; import { EquationBox } from '../views/nodes/EquationBox'; import { FieldViewProps } from '../views/nodes/FieldView'; import { FilterBox } from '../views/nodes/FilterBox'; @@ -1758,14 +1758,14 @@ export namespace DocUtils { return dd; } - async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, rootDoc?: Doc) { + async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) { if (result instanceof Error) { alert(`Upload failed: ${result.message}`); return; } const full = { ...options, _width: 400, title: name }; const pathname = Utils.prepend(result.accessPaths.agnostic.client); - const doc = await DocUtils.DocumentFromType(type, pathname, full, rootDoc); + const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc); if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; @@ -1797,8 +1797,10 @@ export namespace DocUtils { if (Upload.isVideoInformation(result)) { proto['data-duration'] = result.duration; } - if (rootDoc) { - Doc.removeCurrentlyLoading(rootDoc); + if (overwriteDoc) { + Doc.removeCurrentlyLoading(overwriteDoc); + // loading doc icons are just labels. so any icon views of loading docs need to be replaced with the proper icon view. + DocumentManager.Instance.getAllDocumentViews(overwriteDoc).forEach(dv => StrCast(dv.rootDoc.layoutKey) === 'layout_icon' && dv.iconify(() => dv.iconify())); } generatedDocuments.push(doc); } @@ -1915,5 +1917,5 @@ ScriptingGlobals.add(function generateLinkTitle(self: Doc) { return `${anchor1title} (${relation}) ${anchor2title}`; }); ScriptingGlobals.add(function openTabAlias(tab: Doc) { - CollectionDockingView.AddSplit(Doc.MakeAlias(tab), 'right'); + CollectionDockingView.AddSplit(Doc.MakeAlias(tab), OpenWhereMod.right); }); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index c151aebcd..98d0378be 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -685,18 +685,17 @@ export class MainView extends React.Component { mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); addDocTabFunc = (doc: Doc, location: OpenWhere): boolean => { const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); - const whereMods = whereFields.length > 1 ? whereFields[1] : ''; + const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; if (doc.dockingConfig) return DashboardView.openDashboard(doc); // prettier-ignore switch (whereFields[0]) { - default: - case OpenWhere.inPlace: + case OpenWhere.inPlace: // fall through to lightbox case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location); - case OpenWhere.add: return CollectionDockingView.AddSplit(doc, whereMods as OpenWhereMod); case OpenWhere.dashboard: return DashboardView.openDashboard(doc); - case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); - case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods as OpenWhereMod); + case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods); + case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods); } }; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index aa4583af6..ac6391365 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -241,14 +241,6 @@ export class CollectionStackingView extends CollectionSubView this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } - addDocTab = (doc: Doc, where: OpenWhere) => { - 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); }; @@ -361,7 +353,7 @@ export class CollectionStackingView extends CollectionSubView { // inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack addDocTab = (doc: Doc, location: OpenWhere) => { SelectionManager.DeselectAll(); - const locationFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); - const locationParams: OpenWhereMod = locationFields.length > 1 ? (locationFields[1] as OpenWhereMod) : OpenWhereMod.none; - switch (locationFields[0]) { - case OpenWhere.dashboard: - return DashboardView.openDashboard(doc); - case OpenWhere.close: - return CollectionDockingView.CloseSplit(doc, locationParams); - case OpenWhere.fullScreen: - return CollectionDockingView.OpenFullScreen(doc); - case OpenWhere.replace: - return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); - case OpenWhere.inPlace: - const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined; - if (inPlaceView) { - inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List([doc]); - return true; - } // fall through to lightbox - case OpenWhere.lightbox: - return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); - case OpenWhere.toggle: - return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack); - case OpenWhere.add: - default: - return CollectionDockingView.AddSplit(doc, locationParams, this.stack); + const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); + const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; + if (doc.dockingConfig) return DashboardView.openDashboard(doc); + // prettier-ignore + switch (whereFields[0]) { + case OpenWhere.inPlace: // fall through to lightbox + case OpenWhere.lightbox: return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab); + case OpenWhere.dashboard: return DashboardView.openDashboard(doc); + case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); + case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); + case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack); + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack); + case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack); } }; remDocTab = (doc: Doc | Doc[]) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8cabf060d..56a5c3dcc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1330,17 +1330,23 @@ export class CollectionFreeFormView extends CollectionSubView { - if (where === OpenWhere.inParent) { - (doc instanceof Doc ? [doc] : doc).forEach(doc => { - const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); - doc.x = pt[0]; - doc.y = pt[1]; - }); - return this.props.addDocument?.(doc) || false; - } - if (where === OpenWhere.inPlace && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List(doc as any as Doc[]); - return true; + switch (where) { + case OpenWhere.inParent: + return ( + this.props.addDocument?.( + (doc instanceof Doc ? [doc] : doc).map(doc => { + const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); + doc.x = pt[0]; + doc.y = pt[1]; + return doc; + }) + ) || false + ); + case OpenWhere.inPlace: + if (this.layoutDoc.isInPlaceContainer) { + this.dataDoc[this.props.fieldKey] = new List(doc instanceof Doc ? [doc] : doc); + return true; + } } return this.props.addDocTab(doc, where); }); diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index d7f317f4f..407deaabd 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -234,13 +234,6 @@ export class CollectionMultirowView extends CollectionSubView() { onChildClickHandler = () => ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => 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); - }; focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options); isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive(); isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false); @@ -277,7 +270,7 @@ export class CollectionMultirowView extends CollectionSubView() { moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} + addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} /> -- cgit v1.2.3-70-g09d2 From c6d1059e24f362a167b9ac24e6f13d1e45361da9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 29 Nov 2022 12:05:29 -0500 Subject: changes to streamline link editing UI (got rid of LinkEditor). cleaned up link (un)highlighting. --- src/client/documents/Documents.ts | 1 - src/client/util/DocumentManager.ts | 19 +- src/client/util/LinkFollower.ts | 2 +- src/client/util/LinkManager.ts | 2 +- src/client/util/SelectionManager.ts | 1 - src/client/views/DocumentDecorations.tsx | 14 +- src/client/views/MainView.tsx | 2 +- src/client/views/PropertiesView.scss | 15 +- src/client/views/PropertiesView.tsx | 94 +++-- .../CollectionFreeFormLinksView.tsx | 2 +- src/client/views/global/globalCssVariables.scss | 1 + .../views/global/globalCssVariables.scss.d.ts | 1 + src/client/views/linking/LinkEditor.scss | 334 --------------- src/client/views/linking/LinkEditor.tsx | 454 --------------------- src/client/views/linking/LinkMenu.tsx | 37 +- src/client/views/linking/LinkMenuGroup.tsx | 50 ++- src/client/views/linking/LinkMenuItem.tsx | 4 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 16 +- src/client/views/nodes/LinkAnchorBox.tsx | 110 ++--- src/fields/Doc.ts | 64 +-- 21 files changed, 194 insertions(+), 1031 deletions(-) delete mode 100644 src/client/views/linking/LinkEditor.scss delete mode 100644 src/client/views/linking/LinkEditor.tsx (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c5e08eeea..eed839520 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1375,7 +1375,6 @@ export namespace DocUtils { 'acl-Public': SharingPermissions.Augment, '_acl-Public': SharingPermissions.Augment, linkDisplay: true, - _hidden: true, _linkAutoMove: true, linkRelationship, _showCaption: 'description', diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 7120eeb88..4f02a8202 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,7 +1,7 @@ import { action, observable, runInAction } from 'mobx'; import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; -import { Cast } from '../../fields/Types'; +import { Cast, DocCast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { LightboxView } from '../views/LightboxView'; @@ -36,15 +36,14 @@ export class DocumentManager { //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString); if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { const viewAnchorIndex = view.props.LayoutTemplateString.includes('anchor2') ? 'anchor2' : 'anchor1'; - DocListCast(view.rootDoc.links).forEach(link => { - this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView => - this.LinkedDocumentViews.push({ - a: viewAnchorIndex === 'anchor2' ? otherView : view, - b: viewAnchorIndex === 'anchor2' ? view : otherView, - l: link, - }) - ); - }); + const link = view.rootDoc; + this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView => + this.LinkedDocumentViews.push({ + a: viewAnchorIndex === 'anchor2' ? otherView : view, + b: viewAnchorIndex === 'anchor2' ? view : otherView, + l: link, + }) + ); this.LinkAnchorBoxViews.push(view); // this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " + // view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString)); diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 5374bf44b..282116f1b 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -67,7 +67,7 @@ export class LinkFollower { docViewProps.ContainingCollectionDoc, action(() => { batch.end(); - DocumentDecorations.Instance.overrideBounds = false; + Doc.AddUnlightWatcher(action(() => (DocumentDecorations.Instance.overrideBounds = false))); }), altKey ? true : undefined ); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 49cc3218d..01f4df723 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -44,7 +44,7 @@ export class LinkManager { if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { Doc.GetProto(a1)[DirectLinksSym].add(link); Doc.GetProto(a2)[DirectLinksSym].add(link); - Doc.GetProto(link)[DirectLinksSym].add(link); + //Doc.GetProto(link)[DirectLinksSym].add(link); // bcz: links are not linked to themself, so this was a hack } }) ); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 02d672a65..a3d6f5227 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,7 +4,6 @@ import { Doc, Opt } from '../../fields/Doc'; import { DocCast } from '../../fields/Types'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocumentView } from '../views/nodes/DocumentView'; -import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; export namespace SelectionManager { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3efb5fb37..d1f0bf2ac 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -27,7 +27,7 @@ import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; -import { DocumentView } from './nodes/DocumentView'; +import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; import React = require('react'); @@ -253,17 +253,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (selectedDocs.length) { if (e.ctrlKey) { // open an alias in a new tab with Ctrl Key - CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), 'right'); + CollectionDockingView.AddSplit(Doc.BestAlias(selectedDocs[0].props.Document), OpenWhereMod.right); } else if (e.shiftKey) { // open centered in a new workspace with Shift Key const alias = Doc.MakeAlias(selectedDocs[0].props.Document); alias.context = undefined; alias.x = -alias[WidthSym]() / 2; alias.y = -alias[HeightSym]() / 2; - CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), 'right'); + CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: 'Tab for ' + alias.title }), OpenWhereMod.right); } else if (e.altKey) { // open same document in new tab - CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, 'right'); + CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, OpenWhereMod.right); } else { var openDoc = selectedDocs[0].props.Document; if (openDoc.layoutKey === 'layout_icon') { @@ -720,13 +720,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button - const hideOpenButton =hideDecorations || + const hideOpenButton = + hideDecorations || seldocview.props.hideOpenButton || seldocview.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) || this._isRounding || this._isRotating; - const hideDeleteButton =hideDecorations || + const hideDeleteButton = + hideDecorations || this._isRounding || this._isRotating || seldocview.props.hideDeleteButton || diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 98d0378be..09063901d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -484,7 +484,7 @@ export class MainView extends React.Component { } globalPointerDown = action((e: PointerEvent) => { - runInAction(() => (Doc.HighlightBrush.linkFollowEffect = undefined)); + Doc.linkFollowUnhighlight(); AudioBox.Enabled = true; const targets = document.elementsFromPoint(e.x, e.y); if (targets.length) { diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index 30806f718..897be9a32 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -4,8 +4,13 @@ height: 100%; width: 250; font-family: 'Roboto'; + font-size: 12px; cursor: auto; + .slider-text { + font-size: 8px; + } + overflow-x: hidden; overflow-y: auto; @@ -865,7 +870,15 @@ } .propertiesButton { - width: 4rem; + width: 2rem; + height: 2rem; + display: flex; + justify-content: center; + align-items: center; + > svg { + width: 15px; + height: 15px; + } } } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index ad3f62990..e8fd540a8 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -55,7 +55,7 @@ export class PropertiesView extends React.Component { } @computed get selectedDoc() { - return LinkManager.currentLink || SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; + return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; @@ -306,7 +306,8 @@ export class PropertiesView extends React.Component { } @computed get links() { - return !this.selectedDoc ? null : ; + const selAnchor = this.selectedDocumentView?.anchorViewDoc; + return !selAnchor ? null : ; } @computed get layoutPreview() { @@ -1426,11 +1427,11 @@ export class PropertiesView extends React.Component { @undoBatch animationDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { const lanch = this.sourceAnchor; - const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; + const color = lanch?.presEffectDirection === direction || (direction === PresEffectDirection.Center && !lanch?.presEffectDirection) ? Colors.MEDIUM_BLUE : ''; return ( {direction}
}>
this.updateEffectDirection(direction)}> {icon ? : null}
@@ -1470,19 +1471,21 @@ export class PropertiesView extends React.Component { } }; - toggleProp = (e: React.PointerEvent, prop: string) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc[prop] = !this.selectedDoc[prop])))); + toggleLinkProp = (e: React.PointerEvent, prop: string) => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.currentLink && (LinkManager.currentLink[prop] = !LinkManager.currentLink[prop])))); }; @computed get destinationAnchor() { const ldoc = LinkManager.currentLink; - const lanch = LinkManager.currentLinkAnchor; + const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor; if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch; return ldoc ? DocCast(ldoc.anchor2) : ldoc; } @computed get sourceAnchor() { - return LinkManager.currentLinkAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); + const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor; + + return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); } toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc) => { @@ -1545,9 +1548,8 @@ export class PropertiesView extends React.Component { const isNovice = Doc.noviceMode; const zoom = Number((NumCast(this.sourceAnchor?.presZoom, 1) * 100).toPrecision(3)); const targZoom = this.sourceAnchor?.followLinkZoom; - const selectedDoc = this.selectedDoc; const indent = 30; - if (!selectedDoc && !this.isPres) { + if (!this.selectedDoc && !this.isPres) { return (
@@ -1556,7 +1558,7 @@ export class PropertiesView extends React.Component {
); } else { - if (selectedDoc && !this.isPres) { + if (this.selectedDoc && !this.isPres) { return (
{ {this.contextsSubMenu} {this.linksSubMenu} - {!selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(dv.rootDoc.links).includes(LinkManager.currentLink!)) ? null : ( + {!this.selectedDoc || !LinkManager.currentLink || !SelectionManager.Views().some(dv => DocListCast(this.sourceAnchor?.links).includes(LinkManager.currentLink!)) ? null : ( <> -
+

Relationship

{this.editRelationship} @@ -1584,11 +1586,41 @@ export class PropertiesView extends React.Component {

Description

{this.editDescription}
+
+

Show link

+ +
+
+

Auto-move anchors

+ +
+
+

Display arrow

+ +

Follow by

- this.changeFollowBehavior(e.currentTarget.value)} value={StrCast(this.sourceAnchor?.followLinkLocation, 'default')}> @@ -1598,7 +1630,7 @@ export class PropertiesView extends React.Component { - {selectedDoc.linksToAnnotation ? : null} + {LinkManager.currentLink?.linksToAnnotation ? : null}
@@ -1609,7 +1641,7 @@ export class PropertiesView extends React.Component { ))} -
+
{this.animationDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} {this.animationDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} {this.animationDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} @@ -1650,34 +1682,8 @@ export class PropertiesView extends React.Component {
-
-

Show link

- -
-
-

Auto-move anchor

- -
-
-

Display arrow

- -
-
-

Zoom % screen

+
+

Zoom %

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index b8344dc0c..9e360f557 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -2,10 +2,10 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; import { Id } from '../../../../fields/FieldSymbols'; import { DocumentManager } from '../../../util/DocumentManager'; +import { LightboxView } from '../../LightboxView'; import './CollectionFreeFormLinksView.scss'; import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; import React = require('react'); -import { LightboxView } from '../../LightboxView'; @observer export class CollectionFreeFormLinksView extends React.Component> { diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index e68f9abe3..430a36dce 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -84,4 +84,5 @@ $TREE_BULLET_WIDTH: 20px; LEFT_MENU_WIDTH: $LEFT_MENU_WIDTH; TREE_BULLET_WIDTH: $TREE_BULLET_WIDTH; INK_MASK_SIZE: $INK_MASK_SIZE; + MEDIUM_GRAY: $medium-gray; } diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts index 76259113c..3375579d6 100644 --- a/src/client/views/global/globalCssVariables.scss.d.ts +++ b/src/client/views/global/globalCssVariables.scss.d.ts @@ -11,6 +11,7 @@ interface IGlobalScss { LEFT_MENU_WIDTH: string; TREE_BULLET_WIDTH: string; INK_MASK_SIZE: number; + MEDIUM_GRAY: string; } declare const globalCssVariables: IGlobalScss; diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss deleted file mode 100644 index b0ee4e46d..000000000 --- a/src/client/views/linking/LinkEditor.scss +++ /dev/null @@ -1,334 +0,0 @@ -@import '../global/globalCssVariables'; - -.linkEditor { - width: 100%; - height: auto; - font-size: 13px; // TODO - user-select: none; - max-width: 280px; -} - -.linkEditor-button-back { - //margin-bottom: 6px; - border-radius: 10px; - width: 18px; - height: 18px; - padding: 0; - - &:hover { - cursor: pointer; - } -} - -.linkEditor-info { - padding-top: 12px; - padding-left: 5px; - padding-bottom: 3px; - //margin-bottom: 6px; - display: flex; - justify-content: space-between; - color: black; - - .linkEditor-linkedTo { - width: calc(100% - 46px); - overflow: hidden; - position: relative; - text-overflow: ellipsis; - white-space: pre; - - .linkEditor-downArrow { - &:hover { - cursor: pointer; - } - } - } -} - -.linkEditor-moreInfo { - margin-left: 12px; - padding-left: 13px; - padding-right: 6.5px; - padding-bottom: 4px; - font-size: 9px; - //font-style: italic; - text-decoration-color: grey; - - .button { - color: black; - - &:hover { - cursor: pointer; - } - } -} - -.linkEditor-zoomFollow { - padding-left: 26px; - padding-right: 6.5px; - padding-bottom: 3.5px; - display: flex; - - .linkEditor-zoomFollow-label { - text-decoration-color: black; - color: black; - line-height: 1.7; - } - - .linkEditor-zoomFollow-input { - display: block; - width: 20px; - } -} -.linkEditor-deleteBtn { - padding-left: 3px; -} - -.linkEditor-description { - padding-left: 26px; - padding-bottom: 3.5px; - display: flex; - - .linkEditor-description-label { - text-decoration-color: black; - color: black; - } - - .linkEditor-description-input { - display: flex; - - .linkEditor-description-editing { - min-width: 85%; - //border: 1px solid grey; - //border-radius: 4px; - padding-left: 2px; - //margin-right: 4px; - color: black; - text-decoration-color: grey; - } - - .linkEditor-description-add-button { - display: inline; - border-radius: 7px; - font-size: 9px; - background: black; - height: 80%; - color: white; - padding: 3px; - margin-left: 3px; - - &:hover { - cursor: pointer; - background: grey; - } - } - } -} - -.linkEditor-relationship-dropdown { - position: absolute; - width: 154px; - max-height: 90px; - overflow: auto; - background: white; - - p { - padding: 3px; - cursor: pointer; - border: 1px solid $medium-gray; - } - - p:hover { - background: $light-blue; - } -} - -.linkEditor-followingDropdown { - padding-left: 26px; - padding-right: 6.5px; - padding-bottom: 15px; - display: flex; - - &:hover { - cursor: pointer; - } - - .linkEditor-followingDropdown-label { - color: black; - padding-right: 3px; - } - - .linkEditor-followingDropdown-dropdown { - .linkEditor-followingDropdown-header { - border: 1px solid grey; - border-radius: 4px; - //background-color: rgb(236, 236, 236); - padding-left: 2px; - padding-right: 2px; - text-decoration-color: black; - color: rgb(94, 94, 94); - - .linkEditor-followingDropdown-icon { - float: right; - color: black; - } - } - - .linkEditor-followingDropdown-optionsList { - padding-left: 3px; - padding-right: 3px; - - &:last-child { - border-bottom: none; - } - - .linkEditor-followingDropdown-option { - border: 0.25px solid grey; - //background-color: rgb(236, 236, 236); - padding-left: 2px; - padding-right: 2px; - color: grey; - text-decoration-color: grey; - font-size: 9px; - border-top: none; - - &:hover { - background-color: rgb(187, 220, 231); - } - } - } - } -} - -.linkEditor-button, -.linkEditor-addbutton { - width: 15%; - border-radius: 7px; - font-size: 9px; - background: black; - padding: 3px; - height: 80%; - color: white; - text-align: center; - margin: auto; - margin-left: 3px; - > svg { - margin: auto; - } - &:disabled { - background-color: gray; - } -} - -.linkEditor-addbutton { - margin-left: 0px; -} - -.linkEditor-groupsLabel { - display: flex; - justify-content: space-between; -} - -.linkEditor-group { - background-color: $light-gray; - padding: 6px; - margin: 3px 0; - border-radius: 3px; - - .linkEditor-group-row { - display: flex; - margin-bottom: 3px; - } - - .linkEditor-group-row-label { - margin-right: 6px; - display: inline-block; - } - - .linkEditor-metadata-row { - display: flex; - justify-content: space-between; - margin-bottom: 6px; - - .linkEditor-error { - border-color: red; - } - - input { - width: calc(50% - 16px); - height: 20px; - } - - button { - width: 20px; - height: 20px; - margin-left: 3px; - padding: 0; - font-size: 10px; - } - } -} - -.linkEditor-dropdown { - width: 100%; - position: relative; - z-index: 999; - - input { - width: 100%; - } - - .linkEditor-options-wrapper { - width: 100%; - position: absolute; - top: 19px; - left: 0; - display: flex; - flex-direction: column; - } - - .linkEditor-option { - background-color: $light-gray; - border: 1px solid $medium-gray; - border-top: 0; - padding: 3px; - cursor: pointer; - - &:hover { - background-color: lightgray; - } - - &.onDown { - background-color: gray; - } - } -} - -.linkEditor-typeButton { - background-color: transparent; - color: $dark-gray; - height: 20px; - padding: 0 3px; - padding-bottom: 2px; - text-align: left; - text-transform: none; - letter-spacing: normal; - font-size: 12px; - font-weight: bold; - display: inline-block; - width: calc(100% - 40px); - - &:hover { - background-color: $white; - } -} - -.linkEditor-group-buttons { - height: 20px; - display: flex; - justify-content: flex-end; - margin-top: 5px; - - .linkEditor-button { - margin-left: 3px; - } -} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx deleted file mode 100644 index 01e33708a..000000000 --- a/src/client/views/linking/LinkEditor.tsx +++ /dev/null @@ -1,454 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { action, computed, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import { Doc, NumListCast, StrListCast, Field } from '../../../fields/Doc'; -import { DateCast, StrCast, Cast, BoolCast, DocCast, NumCast } from '../../../fields/Types'; -import { LinkManager } from '../../util/LinkManager'; -import { undoBatch } from '../../util/UndoManager'; -import './LinkEditor.scss'; -import { LinkRelationshipSearch } from './LinkRelationshipSearch'; -import React = require('react'); -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; -import { PresBox, PresEffect } from '../nodes/trails'; - -interface LinkEditorProps { - sourceDoc: Doc; - linkDoc: Doc; - showLinks?: () => void; - hideback?: boolean; -} -@observer -export class LinkEditor extends React.Component { - @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); - @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); - @observable zoomFollow = BoolCast(this.props.sourceDoc.followLinkZoom); - @observable audioFollow = BoolCast(this.props.sourceDoc.followLinkAudio); - @observable openDropdown: boolean = false; - @observable openEffectDropdown: boolean = false; - @observable private buttonColor: string = ''; - @observable private relationshipButtonColor: string = ''; - @observable private relationshipSearchVisibility: string = 'none'; - @observable private searchIsActive: boolean = false; - - //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; - - @undoBatch - setRelationshipValue = action((value: string) => { - if (LinkManager.currentLink) { - const prevRelationship = LinkManager.currentLink.linkRelationship as string; - LinkManager.currentLink.linkRelationship = value; - Doc.GetProto(LinkManager.currentLink).linkRelationship = value; - const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); - const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); - const linkColorList = StrListCast(Doc.UserDoc().linkColorList); - - // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color - if (!linkRelationshipList?.includes(value)) { - linkRelationshipList.push(value); - linkRelationshipSizes.push(1); - const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; - linkColorList.push(randColor); - // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes - } else if (linkRelationshipList && value !== prevRelationship) { - const index = linkRelationshipList.indexOf(value); - //increment size of new relationship size - if (index !== -1 && index < linkRelationshipSizes.length) { - const pvalue = linkRelationshipSizes[index]; - linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1; - } - //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) - if (linkRelationshipList.includes(prevRelationship)) { - const pindex = linkRelationshipList.indexOf(prevRelationship); - if (pindex !== -1 && pindex < linkRelationshipSizes.length) { - const pvalue = linkRelationshipSizes[pindex]; - linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1); - } - } - } - this.relationshipButtonColor = 'rgb(62, 133, 55)'; - setTimeout( - action(() => (this.relationshipButtonColor = '')), - 750 - ); - return true; - } - }); - - /** - * returns list of strings with possible existing relationships that contain what is currently in the input field - */ - @action - getRelationshipResults = () => { - const query = this.relationship; //current content in input box - const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList); - if (linkRelationshipList) { - return linkRelationshipList.filter(rel => rel.includes(query)); - } - }; - - /** - * toggles visibility of the relationship search results when the input field is focused on - */ - @action - toggleRelationshipResults = () => { - this.relationshipSearchVisibility = this.relationshipSearchVisibility === 'none' ? 'block' : 'none'; - }; - - @undoBatch - setDescripValue = action((value: string) => { - if (LinkManager.currentLink) { - Doc.GetProto(LinkManager.currentLink).description = value; - this.buttonColor = 'rgb(62, 133, 55)'; - setTimeout( - action(() => (this.buttonColor = '')), - 750 - ); - return true; - } - }); - - onDescriptionKey = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - this.setDescripValue(this.description); - document.getElementById('input')?.blur(); - } - e.stopPropagation(); - }; - - onRelationshipKey = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - this.setRelationshipValue(this.relationship); - document.getElementById('input')?.blur(); - } - e.stopPropagation(); - }; - - onDescriptionDown = () => this.setDescripValue(this.description); - onRelationshipDown = () => this.setRelationshipValue(this.relationship); - - onBlur = () => { - //only hide the search results if the user clicks out of the input AND not on any of the search results - // i.e. if search is not active - if (!this.searchIsActive) { - this.toggleRelationshipResults(); - } - }; - onFocus = () => { - this.toggleRelationshipResults(); - }; - toggleSearchIsActive = () => { - this.searchIsActive = !this.searchIsActive; - }; - - @action - handleDescriptionChange = (e: React.ChangeEvent) => { - this.description = e.target.value; - }; - @action - handleRelationshipChange = (e: React.ChangeEvent) => { - this.relationship = e.target.value; - }; - @action - handleZoomFollowChange = () => { - this.props.sourceDoc.followLinkZoom = !this.props.sourceDoc.followLinkZoom; - }; - @action - handleAudioFollowChange = () => { - this.props.sourceDoc.followLinkAudio = !this.props.sourceDoc.followLinkAudio; - }; - @action - handleRelationshipSearchChange = (result: string) => { - this.setRelationshipValue(result); - this.toggleRelationshipResults(); - this.relationship = result; - }; - @computed - get editRelationship() { - //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return ( -
-
Relationship:
-
-
- - -
-
- Set -
-
-
- ); - } - @computed - get editZoomFollow() { - //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return ( -
-
Zoom To Link Target:
-
-
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.handleZoomFollowChange)} defaultChecked={this.zoomFollow} /> -
-
-
- ); - } - - @computed - get editAudioFollow() { - //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return ( -
-
Play Target Audio:
-
-
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.handleAudioFollowChange)} defaultChecked={this.audioFollow} /> -
-
-
- ); - } - - @computed - get editDescription() { - return ( -
-
Description:
-
-
- -
-
- Set -
-
-
- ); - } - - @action - changeDropdown = () => { - this.openDropdown = !this.openDropdown; - }; - - @undoBatch - changeFollowBehavior = action((follow: string) => { - this.openDropdown = false; - Doc.GetProto(this.props.linkDoc).followLinkLocation = follow; - }); - - @computed - get followingDropdown() { - return ( -
-
Follow by:
-
-
- {StrCast(this.props.linkDoc.followLinkLocation, 'default')} - -
-
-
this.changeFollowBehavior('default')}> - Default -
-
this.changeFollowBehavior('add:left')}> - Always opening in new left pane -
-
this.changeFollowBehavior('add:right')}> - Always opening in new right pane -
-
this.changeFollowBehavior('replace:right')}> - Always replacing right tab -
-
this.changeFollowBehavior('replace:left')}> - Always replacing left tab -
-
this.changeFollowBehavior('fullScreen')}> - Always opening full screen -
-
this.changeFollowBehavior('add')}> - Always opening in a new tab -
-
this.changeFollowBehavior('replace')}> - Replacing Tab -
-
this.changeFollowBehavior('inPlace')}> - Opening in Place -
- {this.props.linkDoc.linksToAnnotation ? ( -
this.changeFollowBehavior('openExternal')}> - Always open in external page -
- ) : null} -
-
-
- ); - } - - @computed get destinationAnchor() { - const ldoc = this.props.linkDoc; - if (this.props.sourceDoc !== ldoc.anchor1 && this.props.sourceDoc !== ldoc.anchor2) { - if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor2); - if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor1); - } - return LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc) ?? this.props.sourceDoc; - } - @action - changeEffectDropdown = () => { - this.openEffectDropdown = !this.openEffectDropdown; - }; - - @undoBatch - changeEffect = action((follow: string) => { - this.openEffectDropdown = false; - this.destinationAnchor.presEffect = follow; - }); - - @computed - get effectDropdown() { - return ( -
-
Animation:
-
-
- {StrCast(this.destinationAnchor.presEffect, 'default')} - -
-
- {[PresEffect.None, PresEffect.Zoom, PresEffect.Lightspeed, PresEffect.Fade, PresEffect.Flip, PresEffect.Rotate, PresEffect.Bounce, PresEffect.Roll].map(effect => ( -
this.changeEffect(effect.toString())}> - {effect.toString()} -
- ))} -
-
-
- ); - } - - autoMove = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove)))); - }; - - showAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden)))); - }; - - showLink = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay)))); - }; - - deleteLink = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this.props.linkDoc)))); - }; - - render() { - const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - - return !destination ? null : ( -
e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> -
- {!this.props.showLinks ? null : ( - Return to link menu
} placement="top"> - - - )} -

- Editing Link to: {StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))} -

- Delete Link
}> -
e.stopPropagation()}> - -
- -
-
- {this.props.linkDoc.author ? ( - <> - {' '} - Author: {StrCast(this.props.linkDoc.author)} - - ) : null} - {this.props.linkDoc.creationDate ? ( - <> - {' '} - Creation Date: - {DateCast(this.props.linkDoc.creationDate).toString()} - - ) : null} -
- {this.editDescription} - {this.editRelationship} - {this.editZoomFollow} - {this.editAudioFollow} -
- Show Anchor: - {this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}
}> -
e.stopPropagation()}> - -
- -
-
- Show Link Line: - {this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}
}> -
e.stopPropagation()}> - -
- -
-
- Freeze Anchor: - {this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}
}> -
e.stopPropagation()}> - -
- -
- {this.followingDropdown} - {this.effectDropdown} - {PresBox.inputter('0.1', '0.1', '10', NumCast(this.destinationAnchor.presTransition) / 1000, true, (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => (this.destinationAnchor.presTransition = timeInMS)))} -
-
Fast
-
Medium
-
Slow
-
{' '} -
- ); - } -} diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 0c46a6d96..c9112eec3 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -1,14 +1,13 @@ -import { action, computed, observable } from 'mobx'; +import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; +import { DocCast } from '../../../fields/Types'; import { LinkManager } from '../../util/LinkManager'; import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; -import { LinkEditor } from './LinkEditor'; import './LinkMenu.scss'; import { LinkMenuGroup } from './LinkMenuGroup'; import React = require('react'); -import { emptyFunction } from '../../../Utils'; interface Props { docView: DocumentView; @@ -23,15 +22,9 @@ interface Props { @observer export class LinkMenu extends React.Component { _editorRef = React.createRef(); - @observable _editingLink?: Doc; @observable _linkMenuRef = React.createRef(); - clear = !this.props.clearLinkEditor - ? undefined - : action(() => { - this.props.clearLinkEditor?.(); - this._editingLink = undefined; - }); + clear = () => this.props.clearLinkEditor?.(); componentDidMount() { this.props.clearLinkEditor && document.addEventListener('pointerdown', this.onPointerDown, true); @@ -43,7 +36,7 @@ export class LinkMenu extends React.Component { onPointerDown = action((e: PointerEvent) => { LinkDocPreview.Clear(); if (!this._linkMenuRef.current?.contains(e.target as any) && !this._editorRef.current?.contains(e.target as any)) { - this.clear?.(); + this.clear(); } }); @@ -54,34 +47,20 @@ export class LinkMenu extends React.Component { */ renderAllGroups = (groups: Map>): Array => { const linkItems = Array.from(groups.entries()).map(group => ( - (this._editingLink = linkDoc))} - /> + )); return linkItems.length ? linkItems : this.props.style ? [<>] : [

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

]; }; render() { - const sourceDoc = this.props.docView.props.Document; + const sourceDoc = this.props.docView.rootDoc; + const sourceAnchor = this.props.docView.anchorViewDoc ?? sourceDoc; const style = this.props.style ?? (dv => ({ left: dv?.left || 0, top: this.props.docView.topMost ? undefined : (dv?.bottom || 0) + 15, bottom: this.props.docView.topMost ? 20 : undefined, maxWidth: 200 }))(this.props.docView.getBounds()); return (
- {this._editingLink ? ( -
- (this._editingLink = undefined))} /> -
- ) : ( -
{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceDoc))}
- )} +
{this.renderAllGroups(LinkManager.Instance.getRelatedGroupedLinks(sourceAnchor))}
); } diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 9d2082e21..d02a1c4eb 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -2,19 +2,19 @@ import { observer } from 'mobx-react'; import { observable, action } from 'mobx'; import { Doc, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; -import { Cast } from '../../../fields/Types'; +import { Cast, DocCast } from '../../../fields/Types'; import { LinkManager } from '../../util/LinkManager'; import { DocumentView } from '../nodes/DocumentView'; import './LinkMenu.scss'; import { LinkMenuItem } from './LinkMenuItem'; import React = require('react'); +import { DocumentType } from '../../documents/DocumentTypes'; interface LinkMenuGroupProps { sourceDoc: Doc; group: Doc[]; groupType: string; clearLinkEditor?: () => void; - showEditor: (linkDoc: Doc) => void; docView: DocumentView; itemHandler?: (doc: Doc) => void; } @@ -44,25 +44,33 @@ export class LinkMenuGroup extends React.Component { render() { const set = new Set(this.props.group); const groupItems = Array.from(set.keys()).map(linkDoc => { - const destination = - LinkManager.getOppositeAnchor(linkDoc, this.props.sourceDoc) || - LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null)); - if (destination && this.props.sourceDoc) { - return ( - - ); - } + const sourceDoc = + this.props.docView.anchorViewDoc ?? + (this.props.docView.rootDoc.type === DocumentType.LINK // + ? this.props.docView.props.LayoutTemplateString?.includes('anchor1') + ? DocCast(linkDoc.anchor1) + : DocCast(linkDoc.anchor2) + : this.props.sourceDoc); + const destDoc = !sourceDoc + ? undefined + : this.props.docView.rootDoc.type === DocumentType.LINK + ? this.props.docView.props.LayoutTemplateString?.includes('anchor1') + ? DocCast(linkDoc.anchor2) + : DocCast(linkDoc.anchor1) + : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) || LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null)); + return !destDoc || !sourceDoc ? null : ( + + ); }); return ( diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index c3705b0e1..fb4c6873e 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -18,6 +18,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import './LinkMenuItem.scss'; import React = require('react'); +import { SelectionManager } from '../../util/SelectionManager'; interface LinkMenuItemProps { groupType: string; @@ -26,7 +27,6 @@ interface LinkMenuItemProps { sourceDoc: Doc; destinationDoc: Doc; clearLinkEditor?: () => void; - showEditor: (linkDoc: Doc) => void; menuRef: React.Ref; itemHandler?: (doc: Doc) => void; } @@ -100,10 +100,10 @@ export class LinkMenuItem extends React.Component { }, emptyFunction, action(() => { + SelectionManager.SelectView(this.props.docView, false); if ((SettingsManager.propertiesWidth ?? 0) < 100) { SettingsManager.propertiesWidth = 250; } - //this.props.showEditor(this.props.linkDoc); }) ); }; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 627487a9e..99fa62fa7 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -305,7 +305,7 @@ export class DocumentLinksButton extends React.Component - {title}
: <>}>{this.linkButtonInner} + {!DocumentLinksButton.LinkEditorDocView ? this.linkButtonInner : {title}
}>{this.linkButtonInner}}
); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2729a5047..a8bea61c9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; import { observer, renderReporter } from 'mobx-react'; -import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; +import { AclAdmin, AclEdit, AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -11,7 +11,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { AudioField } from '../../../fields/URLField'; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; @@ -1156,7 +1156,7 @@ export class DocumentViewInternal extends DocComponent this.props.PanelHeight() || 1; anchorStyleProvider = (doc: Opt, props: Opt, property: string): any => { // prettier-ignore - switch (property) { + switch (property.split(':')[0]) { case StyleProp.ShowTitle: return ''; case StyleProp.PointerEvents: return 'none'; case StyleProp.LinkSource: return this.props.Document; // pass the LinkSource to the LinkAnchorBox @@ -1188,7 +1188,7 @@ export class DocumentViewInternal extends DocComponent !d.hidden); + const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => d.linkDisplay); return filtered.map((link, i) => (
{ linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.screenToLocalTransform().Scale; @computed get linkCountView() { - return (this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton) && - DocumentLinksButton.LinkEditorDocView?.rootDoc !== this.rootDoc ? null : ( + return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton ? null : ( ); } @@ -1654,6 +1653,9 @@ export class DocumentView extends React.Component { startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource); + @computed get anchorViewDoc() { + return this.props.LayoutTemplateString?.includes('anchor2') ? DocCast(this.rootDoc['anchor2']) : this.props.LayoutTemplateString?.includes('anchor1') ? DocCast(this.rootDoc['anchor1']) : this.rootDoc; + } docViewPathFunc = () => this.docViewPath; isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection); diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index be9565452..e89076c1f 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -1,4 +1,3 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; @@ -7,20 +6,17 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; -import { SelectionManager } from '../../util/SelectionManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxBaseComponent } from '../DocComponent'; -import { LinkEditor } from '../linking/LinkEditor'; import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkAnchorBox.scss'; import { LinkDocPreview } from './LinkDocPreview'; import React = require('react'); -import { OpenWhere } from './DocumentView'; -const higflyout = require('@hig/flyout'); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; +import { LinkManager } from '../../util/LinkManager'; +import globalCssVariables = require('../global/globalCssVariables.scss'); +import { SelectionManager } from '../../util/SelectionManager'; @observer export class LinkAnchorBox extends ViewBoxBaseComponent() { @@ -34,15 +30,23 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { _timeout: NodeJS.Timeout | undefined; @observable _x = 0; @observable _y = 0; - @observable _selected = false; - @observable _editing = false; - @observable _forceOpen = false; onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false); + const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource); + setupMoveUpEvents( + this, + e, + this.onPointerMove, + emptyFunction, + (e, doubleTap) => { + if (doubleTap) LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false); + else this.props.select(false); + }, + false + ); }; onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => { - const cdiv = this._ref && this._ref.current && this._ref.current.parentElement; + const cdiv = this._ref?.current?.parentElement; if (!this._isOpen && cdiv) { const bounds = cdiv.getBoundingClientRect(); const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); @@ -60,58 +64,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { } return false; }); - @action - onClick = (e: React.MouseEvent) => { - if (e.button === 2 || e.ctrlKey || !this.layoutDoc.isLinkButton) { - this.props.select(false); - } - if (!this._doubleTap && !e.ctrlKey && e.button < 2) { - const anchorContainerDoc = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource); - this._editing = true; - anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false); - if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) { - this._timeout = setTimeout( - action(() => { - LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false); - this._editing = false; - }), - 300 - (Date.now() - this._lastTap) - ); - e.stopPropagation(); - } - } else { - this._timeout && clearTimeout(this._timeout); - this._timeout = undefined; - this._doubleTap = false; - this.openLinkEditor(e); - e.stopPropagation(); - } - }; - openLinkDocOnRight = (e: React.MouseEvent) => { - this.props.addDocTab(this.rootDoc, OpenWhere.addRight); - }; - openLinkTargetOnRight = (e: React.MouseEvent) => { - const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); - alias._isLinkButton = undefined; - alias.layoutKey = 'layout'; - this.props.addDocTab(alias, OpenWhere.addRight); - }; - @action - openLinkEditor = action((e: React.MouseEvent) => { - SelectionManager.DeselectAll(); - this._editing = this._forceOpen = true; - }); - - specificContextMenu = (e: React.MouseEvent): void => { - const funcs: ContextMenuProps[] = []; - funcs.push({ description: 'Open Link Target on Right', event: () => this.openLinkTargetOnRight(e), icon: 'eye' }); - funcs.push({ description: 'Open Link on Right', event: () => this.openLinkDocOnRight(e), icon: 'eye' }); - funcs.push({ description: 'Open Link Editor', event: () => this.openLinkEditor(e), icon: 'eye' }); - funcs.push({ description: 'Toggle Always Show Link', event: () => (this.props.Document.linkDisplay = !this.props.Document.linkDisplay), icon: 'eye' }); - - ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); - }; + specificContextMenu = (e: React.MouseEvent): void => {}; render() { TraceMobx(); @@ -122,22 +76,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor'); const anchor = this.fieldKey === 'anchor1' ? 'anchor2' : 'anchor1'; const anchorScale = !this.dataDoc[this.fieldKey + '-useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25; - const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title); - const flyout = ( -
Doc.UnBrushDoc(this.rootDoc)}> - {})} /> - {!this._forceOpen ? null : ( -
(this._isOpen = this._editing = this._forceOpen = false))}> - -
- )} -
- ); + const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('anchor1') ? 'anchor1' : SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('anchor2') ? 'anchor2' : ''; return (
LinkDocPreview.SetLinkInfo({ docProps: this.props, @@ -149,24 +94,15 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { }) } onPointerDown={this.onPointerDown} - onClick={this.onClick} - title={targetTitle} onContextMenu={this.specificContextMenu} - ref={this._ref} style={{ + border: selView && this.rootDoc[selView] === this.rootDoc[this.fieldKey] ? `solid ${globalCssVariables.MEDIUM_GRAY} 2px` : undefined, background, left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`, top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`, transform: `scale(${anchorScale})`, - }}> - {!this._editing && !this._forceOpen ? null : ( - (this._isOpen = true)} onClose={action(() => (this._isOpen = this._forceOpen = this._editing = false))}> - - - - - )} -
+ }} + /> ); } } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index c10751698..c6cabe269 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -94,6 +94,8 @@ export function DocListCastOrNull(field: FieldResult) { export const WidthSym = Symbol('Width'); export const HeightSym = Symbol('Height'); +export const AnimationSym = Symbol('Animation'); +export const HighlightSym = Symbol('Highlight'); export const DataSym = Symbol('Data'); export const LayoutSym = Symbol('Layout'); export const FieldsSym = Symbol('Fields'); @@ -329,6 +331,8 @@ export class Doc extends RefField { @observable private ___fieldKeys: any = {}; @observable public [AclSym]: { [key: string]: symbol } = {}; @observable public [DirectLinksSym]: Set = new Set(); + @observable public [AnimationSym]: Opt; + @observable public [HighlightSym]: boolean = false; private [UpdatingFromServer]: boolean = false; private [ForceServerWrite]: boolean = false; @@ -1261,53 +1265,55 @@ export namespace Doc { } export function linkFollowUnhighlight() { - Doc.UnhighlightAll(); + UnhighlightWatchers.length = 0; + highlightedDocs.forEach(doc => Doc.UnHighlightDoc(doc)); document.removeEventListener('pointerdown', linkFollowUnhighlight); - runInAction(() => (HighlightBrush.linkFollowEffect = undefined)); } - let _lastDate = 0; + let UnhighlightWatchers: (() => void)[] = []; + let UnhighlightTimer: any; + export function AddUnlightWatcher(watcher: () => void) { + if (UnhighlightTimer) { + UnhighlightWatchers.push(watcher); + } else watcher(); + } export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true, presEffect?: Doc) { - //linkFollowUnhighlight(); - // runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = undefined)); - // setTimeout(() => runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect))); - runInAction(() => presEffect && (HighlightBrush.linkFollowEffect = presEffect)); - (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs)); + linkFollowUnhighlight(); + (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs, presEffect)); document.removeEventListener('pointerdown', linkFollowUnhighlight); document.addEventListener('pointerdown', linkFollowUnhighlight); - const lastDate = (_lastDate = Date.now()); - window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000); + if (UnhighlightTimer) clearTimeout(UnhighlightTimer); + UnhighlightTimer = window.setTimeout(() => { + UnhighlightWatchers.forEach(watcher => watcher()); + linkFollowUnhighlight(); + UnhighlightTimer = 0; + }, 5000); } - export class HighlightBrush { - @observable HighlightedDoc: Map = new Map(); - @observable static linkFollowEffect: Doc | undefined; - } - const highlightManager = new HighlightBrush(); + var highlightedDocs = new Set(); export function IsHighlighted(doc: Doc) { if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate || doc.opacity === 0) return false; - return highlightManager.HighlightedDoc.get(doc) || highlightManager.HighlightedDoc.get(Doc.GetProto(doc)); + return doc[HighlightSym] || Doc.GetProto(doc)[HighlightSym]; } - export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true) { + export function HighlightDoc(doc: Doc, dataAndDisplayDocs = true, presEffect?: Doc) { runInAction(() => { - highlightManager.HighlightedDoc.set(doc, true); - dataAndDisplayDocs && highlightManager.HighlightedDoc.set(Doc.GetProto(doc), true); + doc[AnimationSym] = presEffect; + highlightedDocs.add(doc); + doc[HighlightSym] = true; + if (dataAndDisplayDocs) { + highlightedDocs.add(Doc.GetProto(doc)); + Doc.GetProto(doc)[HighlightSym] = true; + } }); } export function UnHighlightDoc(doc: Doc) { runInAction(() => { - highlightManager.HighlightedDoc.set(doc, false); - highlightManager.HighlightedDoc.set(Doc.GetProto(doc), false); + highlightedDocs.delete(doc); + highlightedDocs.delete(Doc.GetProto(doc)); + doc[HighlightSym] = Doc.GetProto(doc)[HighlightSym] = false; + doc[AnimationSym] = undefined; }); } - export function UnhighlightAll() { - const mapEntries = highlightManager.HighlightedDoc.keys(); - let docEntry: IteratorResult; - while (!(docEntry = mapEntries.next()).done) { - const targetDoc = docEntry.value; - targetDoc && Doc.UnHighlightDoc(targetDoc); - } - } export function UnBrushAllDocs() { brushManager.BrushedDoc.clear(); } -- cgit v1.2.3-70-g09d2 From 66184a172006de4d4bf72d9da33858e04d298181 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Dec 2022 10:13:03 -0500 Subject: refactored process of following links / jumping to docs and added following options for zoomTime, etc instead of setting temporary fields on docs. --- src/client/documents/Documents.ts | 8 +- src/client/util/DocumentManager.ts | 98 +++--- .../util/Import & Export/DirectoryImportBox.tsx | 334 +++++++++++---------- src/client/util/LinkFollower.ts | 84 +++--- src/client/util/SharingManager.tsx | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/InkingStroke.tsx | 6 +- src/client/views/MainView.tsx | 2 +- src/client/views/MarqueeAnnotator.tsx | 2 +- src/client/views/PropertiesView.tsx | 28 +- .../views/collections/CollectionNoteTakingView.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 5 +- src/client/views/collections/TabDocView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../collectionLinear/CollectionLinearView.tsx | 2 +- .../collectionSchema/CollectionSchemaCells.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 26 +- src/client/views/nodes/ImageBox.tsx | 8 +- src/client/views/nodes/PDFBox.tsx | 13 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 10 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +- src/client/views/nodes/trails/PresBox.tsx | 24 +- src/client/views/pdf/Annotation.tsx | 4 +- src/client/views/pdf/PDFViewer.tsx | 8 +- src/client/views/search/SearchBox.tsx | 2 +- 27 files changed, 354 insertions(+), 353 deletions(-) (limited to 'src/client/documents/Documents.ts') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index eed839520..d13d96dd3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -157,7 +157,7 @@ export class DocumentOptions { _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed - _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target + _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs @@ -272,7 +272,7 @@ export class DocumentOptions { clipWidth?: number; // percent transition from before to after in comparisonBox dockingConfig?: string; annotationOn?: Doc; - isPushpin?: boolean; + followLinkToggle?: boolean; isGroup?: boolean; // whether a collection should use a grouping UI behavior _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document noteType?: string; @@ -1703,7 +1703,7 @@ export namespace DocUtils { } export function LeavePushpin(doc: Doc, annotationField: string) { - if (doc.isPushpin) return undefined; + if (doc.followLinkToggle) return undefined; const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { @@ -1711,7 +1711,7 @@ export namespace DocUtils { title: 'pushpin', label: '', annotationOn: Cast(doc.annotationOn, Doc, null), - isPushpin: true, + followLinkToggle: true, icon: 'map-pin', x: Cast(doc.x, 'number', null), y: Cast(doc.y, 'number', null), diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4f02a8202..1b63b615b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -5,7 +5,7 @@ import { Cast, DocCast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { LightboxView } from '../views/LightboxView'; -import { DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; @@ -171,19 +171,13 @@ export class DocumentManager { }; public jumpToDocument = ( targetDoc: Doc, // document to display - willZoom: boolean, // whether to zoom doc to take up most of screen + options: DocFocusOptions, // options for how to navigate to target createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist docContext: Doc[], // context to load that should contain the target - linkDoc?: Doc, // link that's being followed - closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there - originatingDoc: Opt = undefined, // doc that initiated the display of the target odoc - finished?: () => void, - originalTarget?: Doc, - noSelect?: boolean, - presZoomScale?: number + finished?: () => void ): void => { - originalTarget = originalTarget ?? targetDoc; - const docView = this.getFirstDocumentView(targetDoc, originatingDoc); + const originalTarget = options.originalTarget ?? targetDoc; + const docView = this.getFirstDocumentView(targetDoc, options.originatingDoc); const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? docView?.rootDoc ?? targetDoc : docView?.rootDoc ?? targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field var wasHidden = resolvedTarget.hidden; @@ -195,14 +189,14 @@ export class DocumentManager { } const focusAndFinish = (didFocus: boolean) => { const finalTargetDoc = resolvedTarget; - if (originatingDoc?.isPushpin) { + if (options.toggleTarget) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal finalTargetDoc.hidden = !finalTargetDoc.hidden; } } else { finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); - !noSelect && docView?.select(false); + !options.noSelect && docView?.select(false); } finished?.(); }; @@ -216,9 +210,8 @@ export class DocumentManager { if (annoContainerView.props.Document.layoutKey === 'layout_icon') { annoContainerView.iconify(() => annoContainerView.focus(targetDoc, { + ...options, originalTarget, - willZoom, - scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(true); @@ -232,13 +225,12 @@ export class DocumentManager { } } if (focusView) { - !noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox - if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc); + !options.noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox + if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc); const doFocus = (forceDidFocus: boolean) => - focusView.focus(originalTarget ?? targetDoc, { + focusView.focus(originalTarget, { + ...options, originalTarget, - willZoom, - scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(forceDidFocus || didFocus); @@ -262,13 +254,12 @@ export class DocumentManager { targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden targetDocContext._viewTransition = 'transform 500ms'; targetDocContextView.props.focus(targetDocContextView.rootDoc, { - willZoom, + ...options, + // originalTarget, // needed? 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, presZoomScale) - ); + targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed?*/ }, createViewFunc, docContext, finished)); } return ViewAdjustment.doNothing; }, @@ -281,56 +272,35 @@ export class DocumentManager { finished?.(); } else { // no timecode means we need to find the context view and focus on our target - const findView = (delay: number) => { - const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it - if (retryDocView) { - // we found the target in the context. - Doc.linkFollowHighlight(retryDocView.rootDoc); - retryDocView.focus(targetDoc, { - willZoom, - afterFocus: (didFocus: boolean) => - new Promise(res => { - !noSelect && focusAndFinish(true); - res(ViewAdjustment.doNothing); - }), - }); // focus on the target in the context - } else if (delay > 1000) { - // we didn't find the target, so it must have moved out of the context. Go back to just creating it. - if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); - if (targetDoc.layout) { - // there will no layout for a TEXTANCHOR type document - createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target - } - } else { - setTimeout(() => findView(delay + 200), 200); - } - }; - setTimeout(() => findView(0), 0); + const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it + if (retryDocView) { + // we found the target in the context. + Doc.linkFollowHighlight(retryDocView.rootDoc); + retryDocView.focus(targetDoc, { + ...options, + // originalTarget -- needed? + afterFocus: (didFocus: boolean) => + new Promise(res => { + !options.noSelect && focusAndFinish(true); + res(ViewAdjustment.doNothing); + }), + }); // focus on the target in the context + } else if (targetDoc.layout) { + // there will no layout for a TEXTANCHOR type document + createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target + } } } else { if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') { 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, presZoomScale) - ); + return docContextView.iconify(() => this.jumpToDocument(targetDoc, { ...options, originalTarget }, createViewFunc, docContext.slice(1, docContext.length), finished)); } } // there's no context view so we need to create one first and try again when that finishes createViewFunc( targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - () => - this.jumpToDocument( - targetDoc, - willZoom, - (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), - docContext, - linkDoc, - true /* if target not found, get rid of context just created */, - originatingDoc, - finished, - originalTarget - ) + () => this.jumpToDocument(targetDoc, { ...options, originalTarget }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), docContext, finished) ); } } diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 37571ae01..916eee4b7 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,27 +1,27 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { BatchedArray } from "array-batcher"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { extname } from "path"; -import Measure, { ContentRect } from "react-measure"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast } from "../../../fields/Types"; -import { AcceptableMedia, Upload } from "../../../server/SharedMediaTypes"; -import { Utils } from "../../../Utils"; -import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; -import { Networking } from "../../Network"; -import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; -import { DocumentManager } from "../DocumentManager"; -import "./DirectoryImportBox.scss"; -import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; -import React = require("react"); +import { BatchedArray } from 'array-batcher'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { extname } from 'path'; +import Measure, { ContentRect } from 'react-measure'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; +import { BoolCast, Cast, NumCast } from '../../../fields/Types'; +import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes'; +import { Utils } from '../../../Utils'; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; +import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; +import { Networking } from '../../Network'; +import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; +import { DocumentManager } from '../DocumentManager'; +import './DirectoryImportBox.scss'; +import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; +import React = require('react'); -const unsupported = ["text/html", "text/plain"]; +const unsupported = ['text/html', 'text/plain']; @observer export class DirectoryImportBox extends React.Component { @@ -29,7 +29,7 @@ export class DirectoryImportBox extends React.Component { @observable private top = 0; @observable private left = 0; private dimensions = 50; - @observable private phase = ""; + @observable private phase = ''; private disposer: Opt; @observable private entries: ImportMetadataEntry[] = []; @@ -40,7 +40,9 @@ export class DirectoryImportBox extends React.Component { @observable private uploading = false; @observable private removeHover = false; - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DirectoryImportBox, fieldKey); + } constructor(props: FieldViewProps) { super(props); @@ -71,7 +73,7 @@ export class DirectoryImportBox extends React.Component { handleSelection = async (e: React.ChangeEvent) => { runInAction(() => { this.uploading = true; - this.phase = "Initializing download..."; + this.phase = 'Initializing download...'; }); const docs: Doc[] = []; @@ -79,7 +81,7 @@ export class DirectoryImportBox extends React.Component { const files = e.target.files; if (!files || files.length === 0) return; - const directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0]; + const directory = (files.item(0) as any).webkitRelativePath.split('/', 1)[0]; const validated: File[] = []; for (let i = 0; i < files.length; i++) { @@ -100,7 +102,7 @@ export class DirectoryImportBox extends React.Component { const sizes: number[] = []; const modifiedDates: number[] = []; - runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); + runInAction(() => (this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`)); const batched = BatchedArray.from(validated, { batchSize: 15 }); const uploads = await batched.batchedMapAsync>(async (batch, collector) => { @@ -109,23 +111,28 @@ export class DirectoryImportBox extends React.Component { modifiedDates.push(file.lastModified); }); collector.push(...(await Networking.UploadFilesToServer(batch))); - runInAction(() => this.completed += batch.length); + runInAction(() => (this.completed += batch.length)); }); - await Promise.all(uploads.map(async response => { - const { source: { type }, result } = response; - if (result instanceof Error) { - return; - } - const { accessPaths, exifData } = result; - const path = Utils.prepend(accessPaths.agnostic.client); - const document = type && await DocUtils.DocumentFromType(type, path, { _width: 300 }); - const { data, error } = exifData; - if (document) { - Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); - docs.push(document); - } - })); + await Promise.all( + uploads.map(async response => { + const { + source: { type }, + result, + } = response; + if (result instanceof Error) { + return; + } + const { accessPaths, exifData } = result; + const path = Utils.prepend(accessPaths.agnostic.client); + const document = type && (await DocUtils.DocumentFromType(type, path, { _width: 300 })); + const { data, error } = exifData; + if (document) { + Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); + docs.push(document); + } + }) + ); for (let i = 0; i < docs.length; i++) { const doc = docs[i]; @@ -146,7 +153,7 @@ export class DirectoryImportBox extends React.Component { _height: 500, _chromeHidden: true, x: NumCast(doc.x), - y: NumCast(doc.y) + offset + y: NumCast(doc.y) + offset, }; const parent = this.props.ContainingCollectionView; if (parent) { @@ -154,14 +161,14 @@ export class DirectoryImportBox extends React.Component { if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); } else { - const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("size")]; + const headers = [new SchemaHeaderField('title'), new SchemaHeaderField('size')]; importContainer = Docs.Create.SchemaDocument(headers, docs, options); } - runInAction(() => this.phase = 'External: uploading files to Google Photos...'); + runInAction(() => (this.phase = 'External: uploading files to Google Photos...')); await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer }); - Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); + Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); - DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []); + DocumentManager.Instance.jumpToDocument(importContainer, { willZoom: true }, undefined, []); } runInAction(() => { @@ -169,14 +176,14 @@ export class DirectoryImportBox extends React.Component { this.quota = 1; this.completed = 0; }); - } + }; componentDidMount() { - this.selector.current!.setAttribute("directory", ""); - this.selector.current!.setAttribute("webkitdirectory", ""); + this.selector.current!.setAttribute('directory', ''); + this.selector.current!.setAttribute('webkitdirectory', ''); this.disposer = reaction( () => this.completed, - completed => runInAction(() => this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`) + completed => runInAction(() => (this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`)) ); } @@ -193,7 +200,7 @@ export class DirectoryImportBox extends React.Component { const offset = this.dimensions / 2; this.left = bounds.width / 2 - offset; this.top = bounds.height / 2 - offset; - } + }; @action addMetadataEntry = async () => { @@ -201,8 +208,8 @@ export class DirectoryImportBox extends React.Component { entryDoc.checked = false; entryDoc.key = keyPlaceholder; entryDoc.value = valuePlaceholder; - Doc.AddDocToList(this.props.Document, "data", entryDoc); - } + Doc.AddDocToList(this.props.Document, 'data', entryDoc); + }; @action remove = async (entry: ImportMetadataEntry) => { @@ -217,7 +224,7 @@ export class DirectoryImportBox extends React.Component { } } } - } + }; render() { const dimensions = 50; @@ -228,193 +235,204 @@ export class DirectoryImportBox extends React.Component { const uploading = this.uploading; const showRemoveLabel = this.removeHover; const persistent = this.persistent; - let percent = `${completed / quota * 100}`; - percent = percent.split(".")[0]; - percent = percent.startsWith("100") ? "99" : percent; + let percent = `${(completed / quota) * 100}`; + percent = percent.split('.')[0]; + percent = percent.startsWith('100') ? '99' : percent; const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6; - const message = {this.phase}; - const centerPiece = this.phase.includes("Google Photos") ? - - :
{this.phase}; + const centerPiece = this.phase.includes('Google Photos') ? ( + + ) : ( +
{percent}%
; + color: 'white', + marginLeft: this.left + marginOffset, + }}> + {percent}% +
+ ); return ( - {({ measureRef }) => -
+ {({ measureRef }) => ( +
{message} + position: 'absolute', + display: 'none', + }} + />