diff options
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 15 | ||||
| -rw-r--r-- | src/client/views/nodes/ColorBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 44 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.scss | 7 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 70 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/FilterBox.tsx | 47 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 50 | ||||
| -rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/ScreenshotBox.scss | 7 | ||||
| -rw-r--r-- | src/client/views/nodes/ScreenshotBox.tsx | 181 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.scss | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 47 | ||||
| -rw-r--r-- | src/client/views/nodes/WebBox.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 44 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx | 4 |
16 files changed, 347 insertions, 183 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 1fda4cc5e..092823603 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -37,10 +37,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static animFields = ["_height", "_width", "x", "y", "_scrollTop", "opacity"]; // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; - random(min: number, max: number) { /* min should not be equal to max */ return min + ((Math.abs(this.X * this.Y) * 9301 + 49297) % 233280 / 233280) * (max - min); } get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; } - get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.random(-1, 1) * this.props.jitterRotation}deg)`; } + get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${this.props.jitterRotation}deg)`; } get X() { return this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } get Y() { return this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); } @@ -156,7 +155,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF returnThis = () => this; render() { TraceMobx(); - const backgroundColor = () => this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor); const divProps: DocumentViewProps = { ...this.props, CollectionFreeFormDocumentView: this.returnThis, @@ -175,17 +173,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition), zIndex: this.ZInd, mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any, - display: this.ZInd === -99 ? "none" : undefined, + display: this.ZInd === -99 ? "none" : undefined }} > - - {Doc.UserDoc().renderStyle !== "comic" ? (null) : - <div style={{ width: "100%", height: "100%", position: "absolute" }}> - <svg style={{ transform: `scale(1,${this.props.PanelHeight() / this.props.PanelWidth()})`, transformOrigin: "top left", overflow: "visible" }} viewBox="0 0 12 14"> - <path d="M 7 0 C 9 -1 13 1 12 4 C 11 10 13 12 10 12 C 6 12 7 13 2 12 Q -1 11 0 8 C 1 4 0 4 0 2 C 0 0 1 0 1 0 C 3 0 3 1 7 0" - style={{ stroke: "black", fill: backgroundColor(), strokeWidth: 0.2 }} /> - </svg> - </div>} - <DocumentView {...divProps} ref={action((r: DocumentView | null) => this._contentView = r)} /> </div>; } diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 46c599abe..8da5cd1b1 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -41,7 +41,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== "") { Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + "-color"] = color.hex; } else { - Doc.Layout(view.props.Document)._backgroundColor = color.hex; // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment + Doc.Layout(view.props.Document)._backgroundColor = color.hex + (color.rgb.a ? Math.round(color.rgb.a * 255).toString(16) : ""); // '_backgroundColor' is template specific. 'backgroundColor' would apply to all templates, but has no UI at the moment } } }); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index b53827371..f0a54e4ac 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,46 +1,45 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, Field, AclPrivate } from "../../../fields/Doc"; -import { Cast, StrCast, NumCast } from "../../../fields/Types"; -import { OmitKeys, Without, emptyPath } from "../../../Utils"; +import { AclPrivate, Doc, Opt } from "../../../fields/Doc"; +import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, StrCast } from "../../../fields/Types"; +import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; +import { emptyPath, OmitKeys, Without } from "../../../Utils"; import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionView } from "../collections/CollectionView"; +import { InkingStroke } from "../InkingStroke"; +import { PresElementBox } from "../presentationview/PresElementBox"; +import { SearchBox } from "../search/SearchBox"; +import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; -import { LabelBox } from "./LabelBox"; -import { EquationBox } from "./EquationBox"; -import { FunctionPlotBox } from "./FunctionPlotBox"; -import { SliderBox } from "./SliderBox"; -import { LinkBox } from "./LinkBox"; -import { ScriptingBox } from "./ScriptingBox"; +import { ColorBox } from "./ColorBox"; +import { ComparisonBox } from "./ComparisonBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; -import { FontIconBox } from "./FontIconBox"; +import { EquationBox } from "./EquationBox"; import { FieldView, FieldViewProps } from "./FieldView"; +import { FilterBox } from "./FilterBox"; +import { FontIconBox } from "./FontIconBox"; import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox"; +import { FunctionPlotBox } from "./FunctionPlotBox"; import { ImageBox } from "./ImageBox"; import { KeyValueBox } from "./KeyValueBox"; +import { LabelBox } from "./LabelBox"; +import { LinkAnchorBox } from "./LinkAnchorBox"; +import { LinkBox } from "./LinkBox"; import { PDFBox } from "./PDFBox"; import { PresBox } from "./PresBox"; -import { SearchBox } from "../search/SearchBox"; -import { FilterBox } from "./FilterBox"; -import { ColorBox } from "./ColorBox"; -import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; -import { LinkAnchorBox } from "./LinkAnchorBox"; -import { PresElementBox } from "../presentationview/PresElementBox"; import { ScreenshotBox } from "./ScreenshotBox"; -import { ComparisonBox } from "./ComparisonBox"; +import { ScriptingBox } from "./ScriptingBox"; +import { SliderBox } from "./SliderBox"; import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; -import { InkingStroke } from "../InkingStroke"; import React = require("react"); -import { TraceMobx, GetEffectiveAcl } from "../../../fields/util"; -import { ScriptField } from "../../../fields/ScriptField"; import XRegExp = require("xregexp"); -import { DocumentType } from "../../documents/DocumentTypes"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -226,7 +225,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox, FunctionPlotBox, ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, LinkBox, ScriptingBox, - ScreenshotBox, HTMLtag, ComparisonBox + ScreenshotBox, + HTMLtag, ComparisonBox }} bindings={bindings} jsx={layoutFrame} diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 36572b861..bdbece621 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -4,6 +4,13 @@ border-radius: inherit; } +.documentView-customBorder { + width:100%; + height: 100%; + position: absolute; + top: 0; +} + .documentView-node, .documentView-node-topmost { position: inherit; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8abcc3f6a..22bdfb1cf 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -47,6 +47,7 @@ import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); import { ScriptingBox } from "./ScriptingBox"; +import { FormattedTextBox } from "./formattedText/FormattedTextBox"; const { Howl } = require('howler'); interface Window { @@ -120,6 +121,7 @@ export interface DocumentViewSharedProps { dropAction?: dropActionType; dontRegisterView?: boolean; hideLinkButton?: boolean; + hideCaptions?: boolean; ignoreAutoHeight?: boolean; disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. pointerEvents?: string; @@ -200,7 +202,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); } @computed get nativeWidth() { return this.props.NativeWidth(); } @computed get nativeHeight() { return this.props.NativeHeight(); } - @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); } + @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onfClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); } @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); } @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); } @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); } @@ -385,7 +387,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps SelectionManager.DeselectAll(); } - startDragging(x: number, y: number, dropAction: dropActionType) { + startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) { if (this._mainCont.current) { const dragData = new DragManager.DocumentDragData([this.props.Document]); const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0); @@ -396,7 +398,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps dragData.moveDocument = this.props.moveDocument; const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView())); - DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart }, + DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) }, () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed. } } @@ -453,10 +455,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps }, console.log); UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click"); } else if (!Doc.IsSystem(this.rootDoc)) { - if (this.props.Document.type !== DocumentType.LABEL) { - UndoManager.RunInBatch(() => this.props.addDocTab((this.rootDoc._fullScreenView as Doc) || this.rootDoc, "lightbox"), "double tap"); - SelectionManager.DeselectAll(); - } + UndoManager.RunInBatch(() => + LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.()) + //this.props.addDocTab((this.rootDoc._fullScreenView as Doc) || this.rootDoc, "lightbox") + , "double tap"); + SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself @@ -508,6 +511,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) { if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && + !this.Document.ignoreClick && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { @@ -668,6 +672,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const appearance = cm.findByDescription("UI Controls..."); const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : []; !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" }); + appearanceItems.push({ + description: "Add a Field", event: () => { + const alias = Doc.MakeAlias(this.rootDoc); + alias.layout = FormattedTextBox.LayoutString("newfield"); + alias.title = "newfield"; + alias._yMargin = 10; + alias._height = 35; + alias._width = 100; + alias.syncLayoutFieldWithTitle = true; + alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width); + alias.y = NumCast(this.rootDoc.y); + this.props.addDocument?.(alias); + }, icon: "eye" + }); DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" }); !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" }); @@ -890,22 +908,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps TraceMobx(); const showTitle = this.ShowTitle?.split(":")[0]; const showTitleHover = this.ShowTitle?.includes(":hover"); - const showCaption = StrCast(this.layoutDoc._showCaption); + const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined; const captionView = !showCaption ? (null) : - <div className="documentView-captionWrapper" - style={{ - backgroundColor: StrCast(this.layoutDoc["caption-backgroundColor"]), - color: StrCast(this.layoutDoc["caption-color"]) - }}> - <DocumentContentsView {...OmitKeys(this.props, ['children']).omit} - yMargin={10} - xMargin={10} + <div className="documentView-captionWrapper"> + <FormattedTextBox {...OmitKeys(this.props, ['children']).omit} + yPadding={10} + xPadding={10} + fieldKey={showCaption} + fontSize={Math.min(32, 12 * this.props.ScreenToLocalTransform().Scale)} hideOnLeave={true} styleProvider={this.captionStyleProvider} dontRegisterView={true} - LayoutTemplateString={`<FormattedTextBox {...props} fieldKey={'${showCaption}'}/>`} onClick={this.onClickFunc} - layoutKey={this.finalLayoutKey} /> + /> </div>; const titleView = !showTitle ? (null) : <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{ @@ -960,6 +975,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way + const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined }; + const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc; const boxShadow = highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` : this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined); return <div className={DocumentView.ROOT_DIV} ref={this._mainCont} @@ -975,8 +992,21 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px", border: highlighting && this.borderRounding && highlightStyle === "dashed" ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined, boxShadow, + clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined }}> - {PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc} + {!borderPath.path ? internal : + <> + {/* <div style={{ clipPath: `path('${borderPath.fill}')` }}> + {internal} + </div> */} + {internal} + <div key="border2" className="documentView-customBorder" style={{ pointerEvents: "none" }} > + <svg style={{ overflow: "visible" }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}> + <path d={borderPath.path} style={{ stroke: "black", fill: "transparent", strokeWidth: borderPath.width }} /> + </svg> + </div> + </> + } </div>; } } @@ -1076,6 +1106,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { }), 400); }); + startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource); + 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/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 4f4df32b3..86250c9d1 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -32,8 +32,8 @@ export interface FieldViewProps extends DocumentViewSharedProps { width?: number; background?: string; color?: string; - xMargin?: number; - yMargin?: number; + xPadding?: number; + yPadding?: number; } @observer diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 090099dd8..c97de3402 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -1,8 +1,9 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { computed, observable, action, reaction, runInAction } from "mobx"; +import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, Field, Opt, DocListCastAsync, HeightSym } from "../../../fields/Doc"; +import Select from "react-select"; +import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { List } from "../../../fields/List"; import { RichTextField } from "../../../fields/RichTextField"; @@ -12,24 +13,22 @@ import { Cast, StrCast } from "../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { UserOptions } from "../../util/GroupManager"; +import { Scripting } from "../../util/Scripting"; +import { SelectionManager } from "../../util/SelectionManager"; import { CollectionTreeView } from "../collections/CollectionTreeView"; +import { CollectionView } from "../collections/CollectionView"; import { ViewBoxBaseComponent } from "../DocComponent"; +import { EditableView } from "../EditableView"; import { SearchBox } from "../search/SearchBox"; +import { DashboardToggleButton, DefaultStyleProvider, StyleProp } from "../StyleProvider"; +import { DocumentViewProps } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import './FilterBox.scss'; -import { Scripting } from "../../util/Scripting"; -import { SelectionManager } from "../../util/SelectionManager"; -import { CollectionView } from "../collections/CollectionView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -import Select from "react-select"; -import { UserOptions } from "../../util/GroupManager"; -import { DocumentViewProps } from "./DocumentView"; -import { DefaultStyleProvider, StyleProp } from "../StyleProvider"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { EditableView } from "../EditableView"; -import { undoBatch } from "../../util/UndoManager"; type FilterBoxDocument = makeInterface<[typeof documentSchema]>; const FilterBoxDocument = makeInterface(documentSchema); @@ -135,7 +134,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc } gatherFieldValues(dashboard: Doc, facetKey: string) { - const childDocs = DocListCast((dashboard.data as any)[0].data); + const childDocs = DocListCast(dashboard.data); const valueSet = new Set<string>(); let rtFields = 0; childDocs.forEach((d) => { @@ -206,7 +205,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc let nonNumbers = 0; let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; facetValues.strings.map(val => { - const num = Number(val); + const num = val ? Number(val) : Number.NaN; if (Number.isNaN(num)) { nonNumbers++; } else { @@ -217,8 +216,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc let newFacet: Opt<Doc>; if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) { newFacet = Docs.Create.TextDocument("", { - _width: 100, _height: 25, system: true, _stayInCollection: true, target: targetDoc, - treeViewExpandedView: "layout", title: facetHeader, _treeViewOpen: true, _forceActive: true, ignoreClick: true + title: facetHeader, system: true, target: targetDoc, _width: 100, _height: 25, _stayInCollection: true, + treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true }); Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox newFacet._textBoxPadding = 4; @@ -226,7 +225,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" }); } else if (facetHeader !== "tags" && nonNumbers / facetValues.strings.length < .1) { newFacet = Docs.Create.SliderDocument({ - title: facetHeader, _overflow: "visible", _fitWidth: true, target: targetDoc, _height: 40, _stayInCollection: true, treeViewExpandedView: "layout", _treeViewOpen: true + title: facetHeader, system: true, target: targetDoc, _fitWidth: true, _height: 40, _stayInCollection: true, + treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, _overflow: "visible", }); const newFacetField = Doc.LayoutFieldKey(newFacet); const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader); @@ -346,16 +346,9 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc * add lock and hide button decorations for the "Dashboards" flyout TreeView */ FilterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => { - switch (property.split(":")[0]) { - case StyleProp.Decorations: - return !doc || doc.treeViewHideHeaderFields ? (null) : - <> - <div className={`styleProvider-treeView-hide${doc.hidden ? "-active" : ""}`} onClick={undoBatch(e => - this.removeFilter(StrCast(doc.title)) - )}> - <FontAwesomeIcon icon={"trash"} size="sm" /> - </div> - </>; + if (property.split(":")[0] === StyleProp.Decorations) { + return !doc || doc.treeViewHideHeaderFields ? (null) : + DashboardToggleButton(doc, "hidden", "trash", "trash", () => this.removeFilter(StrCast(doc.title))); } return this.props.styleProvider?.(doc, props, property); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 8a6946b78..e2e08a0e6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,14 +1,13 @@ -import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap, untracked } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; -import { Dictionary } from 'typescript-collections'; import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc'; import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; -import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { createSchema, makeInterface } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnOne, Utils } from '../../../Utils'; @@ -30,8 +29,6 @@ import React = require("react"); const path = require('path'); export const pageSchema = createSchema({ - _curPage: "number", - fitWidth: "boolean", googlePhotosUrl: "string", googlePhotosTags: "string" }); @@ -59,8 +56,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp 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 componentDidMount() { + this.props.setContentView?.(this); // bcz: do not remove this. without it, stepping into an image in the lightbox causes an infinite loop.... this._disposers.sizer = reaction(() => ( { forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes, @@ -102,8 +103,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp const targetDoc = layoutDoc[DataSym]; if (targetDoc[targetField] instanceof ImageField) { this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField); - Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc)); - Doc.SetNativeWidth(this.dataDoc, Doc.NativeHeight(targetDoc)); + Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey); + Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey); e.stopPropagation(); } } @@ -315,6 +316,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @observable _marqueeing: number[] | undefined; @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @computed get annotationLayer() { + TraceMobx(); return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />; } @action @@ -331,7 +333,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp TraceMobx(); const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / (this.props.scaling?.() || 1)}px` : borderRad; - return (<div className={`imageBox`} onContextMenu={this.specificContextMenu} ref={this._mainCont} + return (<div className="imageBox" onContextMenu={this.specificContextMenu} ref={this._mainCont} style={{ width: this.props.PanelWidth() ? undefined : `100%`, height: this.props.PanelWidth() ? undefined : `100%`, @@ -340,32 +342,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp }} > <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - CollectionView={undefined} - PanelHeight={this.props.PanelHeight} - scaling={returnOne} - ScreenToLocalTransform={this.screenToLocalTransform} - PanelWidth={this.props.PanelWidth} fieldKey={this.annotationKey} + CollectionView={undefined} isAnnotationOverlay={true} - docFilters={this.props.docFilters} - docRangeFilters={this.props.docRangeFilters} - searchFilterDocs={this.props.searchFilterDocs} - removeDocument={this.removeDocument} - moveDocument={this.moveDocument} - addDocument={this.addDocument} annotationLayerHostsContent={true} - focus={this.props.focus} - isSelected={this.props.isSelected} + PanelWidth={this.props.PanelWidth} + PanelHeight={this.props.PanelHeight} + ScreenToLocalTransform={this.screenToLocalTransform} select={emptyFunction} isContentActive={this.isContentActive} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}> + scaling={returnOne} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + removeDocument={this.removeDocument} + moveDocument={this.moveDocument} + addDocument={this.addDocument}> {this.contentFunc} </CollectionFreeFormView> {this.annotationLayer} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : - <MarqueeAnnotator rootDoc={this.rootDoc} - scrollTop={0} down={this._marqueeing} + <MarqueeAnnotator + rootDoc={this.rootDoc} + scrollTop={0} + down={this._marqueeing} scaling={this.props.scaling} docView={this.props.docViewPath().lastElement()} addDocument={this.addDocument} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index d5056d934..feaeb9e21 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -243,6 +243,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </div>; } + anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; + @computed get renderPdfView() { TraceMobx(); return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} @@ -258,7 +260,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps pdf={this._pdf!} url={this.pdfUrl!.url.pathname} isContentActive={this.isContentActive} - anchorMenuClick={this._sidebarRef.current?.anchorMenuClick} + anchorMenuClick={this.anchorMenuClick} loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined} setPdfViewer={this.setPdfViewer} addDocument={this.addDocument} diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss index ab54cf526..6fb5ea7b3 100644 --- a/src/client/views/nodes/ScreenshotBox.scss +++ b/src/client/views/nodes/ScreenshotBox.scss @@ -10,6 +10,13 @@ // } } +#CANCAN { + canvas { + width:100% !important; + height: 100% !important; + } +} + .screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-cont-fullScreen { width: 100%; z-index: -1; // 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index c4f8d1143..252c029e4 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -1,7 +1,9 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable } from "mobx"; +// import { Canvas } from '@react-three/fiber'; +import { action, computed, observable, reaction } from "mobx"; import { observer } from "mobx-react"; +// import { BufferAttribute, Camera, Vector2, Vector3 } from 'three'; import { DateField } from "../../../fields/DateField"; import { Doc, WidthSym } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; @@ -12,7 +14,7 @@ import { ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; import { AudioField, VideoField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, OmitKeys, returnFalse, returnOne, Utils } from "../../../Utils"; +import { emptyFunction, numberRange, OmitKeys, returnFalse, returnOne, Utils } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { Networking } from "../../Network"; @@ -33,18 +35,101 @@ declare class MediaRecorder { type ScreenshotDocument = makeInterface<[typeof documentSchema]>; const ScreenshotDocument = makeInterface(documentSchema); +// interface VideoTileProps { +// raised: { coord: Vector2, off: Vector3 }[]; +// setRaised: (r: { coord: Vector2, off: Vector3 }[]) => void; +// x: number; +// y: number; +// rootDoc: Doc; +// color: string; +// } + +// @observer +// export class VideoTile extends React.Component<VideoTileProps> { +// @observable _videoRef: HTMLVideoElement | undefined; +// _mesh: any = undefined; + +// render() { +// const topLeft = [this.props.x, this.props.y]; +// const raised = this.props.raised; +// const find = (raised: { coord: Vector2, off: Vector3 }[], what: Vector2) => raised.find(r => r.coord.x === what.x && r.coord.y === what.y); +// const tl1 = find(raised, new Vector2(topLeft[0], topLeft[1] + 1)); +// const tl2 = find(raised, new Vector2(topLeft[0] + 1, topLeft[1] + 1)); +// const tl3 = find(raised, new Vector2(topLeft[0] + 1, topLeft[1])); +// const tl4 = find(raised, new Vector2(topLeft[0], topLeft[1])); +// const quad_indices = [0, 2, 1, 0, 3, 2]; +// const quad_uvs = [0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]; +// const quad_normals = [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,]; +// const quad_vertices = +// [ +// topLeft[0] - 0.0 + (tl1?.off.x || 0), topLeft[1] + 1.0 + (tl1?.off.y || 0), 0.0 + (tl1?.off.z || 0), +// topLeft[0] + 1.0 + (tl2?.off.x || 0), topLeft[1] + 1.0 + (tl2?.off.y || 0), 0.0 + (tl2?.off.z || 0), +// topLeft[0] + 1.0 + (tl3?.off.x || 0), topLeft[1] - 0.0 + (tl3?.off.y || 0), 0.0 + (tl3?.off.z || 0), +// topLeft[0] - 0.0 + (tl4?.off.x || 0), topLeft[1] - 0.0 + (tl4?.off.y || 0), 0.0 + (tl4?.off.z || 0) +// ]; + +// const vertices = new Float32Array(quad_vertices); +// const normals = new Float32Array(quad_normals); +// const uvs = new Float32Array(quad_uvs); // Each vertex has one uv coordinate for texture mapping +// const indices = new Uint32Array(quad_indices); // Use the four vertices to draw the two triangles that make up the square. +// const popOut = () => NumCast(this.props.rootDoc.popOut); +// const popOff = () => NumCast(this.props.rootDoc.popOff); +// return ( +// <mesh key={`mesh${topLeft[0]}${topLeft[1]}`} onClick={action(async e => { +// this.props.setRaised([ +// { coord: new Vector2(topLeft[0], topLeft[1]), off: new Vector3(-popOff(), -popOff(), popOut()) }, +// { coord: new Vector2(topLeft[0] + 1, topLeft[1]), off: new Vector3(popOff(), -popOff(), popOut()) }, +// { coord: new Vector2(topLeft[0], topLeft[1] + 1), off: new Vector3(-popOff(), popOff(), popOut()) }, +// { coord: new Vector2(topLeft[0] + 1, topLeft[1] + 1), off: new Vector3(popOff(), popOff(), popOut()) } +// ]); +// if (!this._videoRef) { +// (navigator.mediaDevices as any).getDisplayMedia({ video: true }).then(action((stream: any) => { +// //const videoSettings = stream.getVideoTracks()[0].getSettings(); +// this._videoRef = document.createElement("video"); +// Object.assign(this._videoRef, { +// srcObject: stream, +// //height: videoSettings.height, +// //width: videoSettings.width, +// autoplay: true +// }); +// })); +// } +// })} ref={(r: any) => this._mesh = r}> +// <bufferGeometry attach="geometry" ref={(r: any) => { +// // itemSize = 3 because there are 3 values (components) per vertex +// r?.setAttribute('position', new BufferAttribute(vertices, 3)); +// r?.setAttribute('normal', new BufferAttribute(normals, 3)); +// r?.setAttribute('uv', new BufferAttribute(uvs, 2)); +// r?.setIndex(new BufferAttribute(indices, 1)); +// }} /> +// {!this._videoRef ? <meshStandardMaterial color={this.props.color} /> : +// <meshBasicMaterial > +// <videoTexture attach="map" args={[this._videoRef]} /> +// </meshBasicMaterial>} +// </mesh> +// ); +// } +// } + @observer export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ScreenshotDocument>(ScreenshotDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); } - private _videoRef: HTMLVideoElement | null = null; private _audioRec: any; private _videoRec: any; + @observable private _videoRef: HTMLVideoElement | null = null; @observable _screenCapture = false; @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); } constructor(props: any) { super(props); - this.setupDictation(); + if (this.rootDoc.videoWall) { + this.rootDoc.nativeWidth = undefined; + this.rootDoc.nativeHeight = undefined; + this.layoutDoc.popOff = 0; + this.layoutDoc.popOut = 1; + } else { + this.setupDictation(); + } } getAnchor = () => { const startTime = Cast(this.layoutDoc._currentTimecode, "number", null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined); @@ -67,6 +152,16 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl componentDidMount() { this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0; this.props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + // this.rootDoc.videoWall && reaction(() => ({ width: this.props.PanelWidth(), height: this.props.PanelHeight() }), + // ({ width, height }) => { + // if (this._camera) { + // const angle = -Math.abs(1 - width / height); + // const xz = [0, (this._numScreens - 2) / Math.abs(1 + angle)]; + // this._camera.position.set(this._numScreens / 2 + xz[1] * Math.sin(angle), this._numScreens / 2, xz[1] * Math.cos(angle)); + // this._camera.lookAt(this._numScreens / 2, this._numScreens / 2, 0); + // (this._camera as any).updateProjectionMatrix(); + // } + // }); } componentWillUnmount() { const ind = DocUtils.ActiveRecordings.indexOf(this); @@ -79,26 +174,50 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl } @computed get content() { + if (this.rootDoc.videoWall) return (null); const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive"; return <video className={"videoBox-content" + interactive} key="video" ref={r => { this._videoRef = r; setTimeout(() => { - if (this.rootDoc.mediaState === "pendingRecording" && this._videoRef) { // TODO glr: use mediaState + if (this.rootDoc.mediaState === "pendingRecording" && this._videoRef) { this.toggleRecording(); } - }, 1000); + }, 100); }} - autoPlay={true} - style={{ width: "100%", height: "100%" }} + autoPlay={this._screenCapture} + style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }} onCanPlay={this.videoLoad} controls={true} onClick={e => e.preventDefault()}> <source type="video/mp4" /> Not supported. - </video>; + </video>; } + // _numScreens = 5; + // _camera: Camera | undefined; + // @observable _raised = [] as { coord: Vector2, off: Vector3 }[]; + // @action setRaised = (r: { coord: Vector2, off: Vector3 }[]) => this._raised = r; + @computed get threed() { + // if (this.rootDoc.videoWall) { + // const screens: any[] = []; + // const colors = ["yellow", "red", "orange", "brown", "maroon", "gray"]; + // let count = 0; + // numberRange(this._numScreens).forEach(x => numberRange(this._numScreens).forEach(y => screens.push( + // <VideoTile rootDoc={this.rootDoc} color={colors[count++ % colors.length]} x={x} y={y} raised={this._raised} setRaised={this.setRaised} />))); + // return <Canvas key="canvas" id="CANCAN" style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }} gl={{ antialias: false }} colorManagement={false} onCreated={props => { + // this._camera = props.camera; + // props.camera.position.set(this._numScreens / 2, this._numScreens / 2, this._numScreens - 2); + // props.camera.lookAt(this._numScreens / 2, this._numScreens / 2, 0); + // }}> + // {/* <ambientLight />*/} + // <pointLight position={[10, 10, 10]} intensity={1} /> + // {screens} + // </ Canvas>; + // } + return (null); + } toggleRecording = action(async () => { this._screenCapture = !this._screenCapture; if (this._screenCapture) { @@ -155,7 +274,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl dictationTextProto.mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState"); this.dataDoc[this.fieldKey + "-dictation"] = dictationText; } - contentFunc = () => [this.content]; + contentFunc = () => [this.threed, this.content]; videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 1) * this.props.PanelWidth(); formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()); render() { @@ -183,28 +302,26 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl {this.contentFunc} </CollectionFreeFormView></div> <div style={{ position: "relative", height: this.formattedPanelHeight() }}> - <FormattedTextBox {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} - Document={this.dataDoc[this.fieldKey + "-dictation"]} - fieldKey={"text"} - PanelHeight={this.formattedPanelHeight} - PanelWidth={this.props.PanelWidth} - focus={this.props.focus} - isSelected={this.props.isSelected} - isAnnotationOverlay={true} - select={emptyFunction} - isContentActive={returnFalse} - scaling={returnOne} - xMargin={25} - yMargin={10} - whenChildContentsActiveChanged={emptyFunction} - removeDocument={returnFalse} - moveDocument={returnFalse} - addDocument={returnFalse} - CollectionView={undefined} - ScreenToLocalTransform={this.props.ScreenToLocalTransform} - renderDepth={this.props.renderDepth + 1} - ContainingCollectionDoc={this.props.ContainingCollectionDoc}> - </FormattedTextBox></div> + {!(this.dataDoc[this.fieldKey + "-dictation"] instanceof Doc) ? (null) : + <FormattedTextBox {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} + Document={this.dataDoc[this.fieldKey + "-dictation"]} + fieldKey={"text"} + PanelHeight={this.formattedPanelHeight} + isAnnotationOverlay={true} + select={emptyFunction} + isContentActive={returnFalse} + scaling={returnOne} + xPadding={25} + yPadding={10} + whenChildContentsActiveChanged={emptyFunction} + removeDocument={returnFalse} + moveDocument={returnFalse} + addDocument={returnFalse} + CollectionView={undefined} + renderDepth={this.props.renderDepth + 1} + ContainingCollectionDoc={this.props.ContainingCollectionDoc}> + </FormattedTextBox>} + </div> </div> {!this.props.isSelected() ? (null) : <div className="screenshotBox-uiButtons"> <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} > diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 30f0c4393..f593f74fb 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -66,6 +66,7 @@ background-color:rgba(50, 50, 50, 0.2); transform-origin: left top; pointer-events:all; + cursor: default; } .videoBox-timelineButton { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index fb58ddefc..263fd5a19 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -26,6 +26,8 @@ import { StyleProp } from "../StyleProvider"; import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from "./LinkDocPreview"; import "./VideoBox.scss"; +import { DragManager } from "../../util/DragManager"; +import { DocumentManager } from "../../util/DocumentManager"; const path = require('path'); type VideoDocument = makeInterface<[typeof documentSchema]>; @@ -137,7 +139,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } } - @action public Snapshot() { + @action public Snapshot(downX?: number, downY?: number) { const width = (this.layoutDoc._width || 0); const height = (this.layoutDoc._height || 0); const canvas = document.createElement('canvas'); @@ -176,11 +178,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp const retitled = StrCast(this.rootDoc.title).replace(/[ -\.]/g, ""); const filename = path.basename(encodeURIComponent("snapshot" + retitled + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_"))); VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => - returnedFilename && this.createRealSummaryLink(returnedFilename)); + returnedFilename && this.createRealSummaryLink(returnedFilename, downX, downY)); } } - private createRealSummaryLink = (relative: string) => { + private createRealSummaryLink = (relative: string, downX?: number, downY?: number) => { const url = this.choosePath(Utils.prepend(relative)); const width = this.layoutDoc._width || 1; const height = this.layoutDoc._height || 0; @@ -192,7 +194,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp Doc.SetNativeWidth(Doc.GetProto(imageSummary), Doc.NativeWidth(this.layoutDoc)); Doc.SetNativeHeight(Doc.GetProto(imageSummary), Doc.NativeHeight(this.layoutDoc)); this.props.addDocument?.(imageSummary); - DocUtils.MakeLink({ doc: imageSummary }, { doc: this.getAnchor() }, "video snapshot"); + const link = DocUtils.MakeLink({ doc: imageSummary }, { doc: this.getAnchor() }, "video snapshot"); + link && (Doc.GetProto(link.anchor2 as Doc).timecodeToHide = NumCast((link.anchor2 as Doc).timecodeToShow) + 3); + setTimeout(() => + (downX !== undefined && downY !== undefined) && DocumentManager.Instance.getFirstDocumentView(imageSummary)?.startDragging(downX, downY, "move", true)); } @action @@ -383,7 +388,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp <span>{"" + formatTime(curTime)}</span> <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span> </div>, - <div className="videoBox-snapshot" key="snap" onClick={this.onSnapshot} > + <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshotDown} > <FontAwesomeIcon icon="camera" size="lg" /> </div>, <div className="videoBox-timelineButton" key="timeline" onPointerDown={this.onTimelineHdlDown} style={{ bottom: `${100 - this.heightPercent}%` }}> @@ -409,10 +414,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp e.preventDefault(); } - onSnapshot = (e: React.MouseEvent) => { - this.Snapshot(); - e.stopPropagation(); - e.preventDefault(); + onSnapshotDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, (e) => { + this.Snapshot(e.clientX, e.clientY); + return true; + }, emptyFunction, () => this.Snapshot()); } onTimelineHdlDown = action((e: React.PointerEvent) => { @@ -492,7 +498,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } playLink = (doc: Doc) => { - const startTime = Math.max(0, (this._stackedTimeline.current?.anchorStart(doc) || 0) - .25); + const startTime = Math.max(0, (this._stackedTimeline.current?.anchorStart(doc) || 0)); const endTime = this._stackedTimeline.current?.anchorEnd(doc); if (startTime !== undefined) { if (!this.layoutDoc.dontAutoPlayFollowedLinks) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime); @@ -567,22 +573,22 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp <div className="videoBox-viewer" onPointerDown={this.marqueeDown} > <div style={{ position: "absolute", transition: this.transition, width: this.panelWidth(), height: this.panelHeight(), top: 0, left: `${(100 - this.heightPercent) / 2}%` }}> <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} + renderDepth={this.props.renderDepth + 1} fieldKey={this.annotationKey} + CollectionView={undefined} isAnnotationOverlay={true} annotationLayerHostsContent={true} - select={emptyFunction} - isContentActive={this.isContentActive} - scaling={returnOne} - docFilters={this.timelineDocFilter} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} ScreenToLocalTransform={this.screenToLocalTransform} + docFilters={this.timelineDocFilter} + select={emptyFunction} + isContentActive={this.isContentActive} + scaling={returnOne} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} - addDocument={this.addDocWithTimecode} - CollectionView={undefined} - renderDepth={this.props.renderDepth + 1}> + addDocument={this.addDocWithTimecode}> {this.contentFunc} </CollectionFreeFormView> </div> @@ -591,16 +597,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp {this.renderTimeline} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : <MarqueeAnnotator - scrollTop={0} rootDoc={this.rootDoc} + scrollTop={0} down={this._marqueeing} - docView={this.props.docViewPath().lastElement()} scaling={this.marqueeFitScaling} + docView={this.props.docViewPath().lastElement()} containerOffset={this.marqueeOffset} addDocument={this.addDocWithTimecode} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} - annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} + annotationLayer={this._annotationLayer.current} + mainCont={this._mainCont.current} />} </div> </div >); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index fc6f9ceab..cb7e58559 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -478,6 +478,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop)); + anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; render() { const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false; const scale = this.props.scaling?.() || 1; @@ -531,7 +532,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <MarqueeAnnotator rootDoc={this.rootDoc} iframe={this.isFirefox() ? this.iframeClick : undefined} iframeScaling={this.isFirefox() ? this.iframeScaling : undefined} - anchorMenuClick={this._sidebarRef.current?.anchorMenuClick} + anchorMenuClick={this.anchorMenuClick} scrollTop={0} down={this._marqueeing} scaling={returnOne} addDocument={this.addDocument} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c2860af76..911ec1560 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { isEqual } from "lodash"; -import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, runInAction, observable } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap, selectAll } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -68,8 +68,8 @@ const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar) - xMargin?: number; // used to override document's settings for xMargin --- see CollectionCarouselView - yMargin?: number; + xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView + yPadding?: number; noSidebar?: boolean; dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded } @@ -1064,8 +1064,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); } setupEditor(config: any, fieldKey: string) { - const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null); - const rtfField = Cast((!curText?.Text && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField); + const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.props.fieldKey]); + const rtfField = Cast((!curText && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField); if (this.ProseRef) { const self = this; this._editorView?.destroy(); @@ -1075,8 +1075,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const docPos = editorView.coordsAtPos(editorView.state.selection.to); const viewRect = self._ref.current!.getBoundingClientRect(); const scrollRef = self._scrollRef.current; - if ((docPos.bottom < viewRect.top || docPos.bottom > viewRect.bottom) && scrollRef) { - const scrollPos = scrollRef.scrollTop + (docPos.bottom - viewRect.top) * self.props.ScreenToLocalTransform().Scale; + const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined; + const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined; + if ((topOff || botOff) && scrollRef) { + const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE); + const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale; if (this._focusSpeed !== undefined) { scrollPos && smoothScroll(this._focusSpeed, scrollRef, scrollPos); } else { @@ -1118,7 +1121,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks); this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); FormattedTextBox.SelectOnLoadChar = ""; - } else if (curText?.Text && !FormattedTextBox.DontSelectInitialText) { + } else if (curText && !FormattedTextBox.DontSelectInitialText) { selectAll(this._editorView!.state, this._editorView?.dispatch); this.startUndoTypingBatch(); } @@ -1132,6 +1135,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } componentWillUnmount() { + FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined); Object.values(this._disposers).forEach(disposer => disposer?.()); this.endUndoTypingBatch(); this.unhighlightSearchTerms(); @@ -1236,8 +1240,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action onFocused = (e: React.FocusEvent): void => { //applyDevTools.applyDevTools(this._editorView); + FormattedTextBox.Focused = this; this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); } + @observable public static Focused: FormattedTextBox | undefined; onClick = (e: React.MouseEvent): void => { if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) { @@ -1339,7 +1345,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._undoTyping = undefined; return wasUndoing; } + @action onBlur = (e: any) => { + FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined); if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } @@ -1472,8 +1480,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp NativeHeight={returnZero} PanelHeight={this.props.PanelHeight} PanelWidth={this.sidebarWidth} - xMargin={0} - yMargin={0} + xPadding={0} + yPadding={0} scaleField={this.SidebarKey + "-scale"} isAnnotationOverlay={false} select={emptyFunction} @@ -1505,10 +1513,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; - const margins = NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0); + const margins = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); const selPad = Math.min(margins, 10); const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0); const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : ""; + const col = this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color); + const back = this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); return ( <div className="formattedTextBox-cont" onWheel={e => this.isContentActive() && e.stopPropagation()} @@ -1524,12 +1534,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp style={{ overflow: this.autoHeight ? "hidden" : undefined, height: this.props.height || (this.autoHeight && this.props.renderDepth ? "max-content" : undefined), - background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor)), - color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color)), - pointerEvents: interactive ? undefined : "none", - fontSize: this.props.fontSize || Cast(this.layoutDoc._fontSize, "string", null), + background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), + color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), + fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), fontWeight: Cast(this.layoutDoc._fontWeight, "number", null), fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), + pointerEvents: interactive ? undefined : "none", }} onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyDown} @@ -1551,7 +1561,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onScroll={this.onScroll} onDrop={this.ondrop} > <div className={minimal ? "formattedTextBox-minimal" : `formattedTextBox-inner${rounded}${selPaddingClass}`} ref={this.createDropTarget} style={{ - padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${padding}px`, + padding: StrCast(this.layoutDoc._textBoxPadding, `${padding}px`), pointerEvents: !active && !SnappingManager.GetIsDragging() ? (this.layoutDoc.isLinkButton ? "none" : undefined) : undefined }} /> @@ -1560,7 +1570,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp {(this.props.noSidebar || this.Document._noSidebar) || this.Document._singleLine ? (null) : this.sidebarHandle} {!this.layoutDoc._showAudio ? (null) : this.audioHandle} </div> - </div> + </div > ); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 6821935a0..1fde6e5f0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -85,7 +85,7 @@ export class FormattedTextBoxComment { static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "") { FormattedTextBoxComment.textBox = textBox; if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { - FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ")); + FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h)); } } @@ -109,7 +109,7 @@ export class FormattedTextBoxComment { } // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. - if (state.selection.$from && hrefs) { + if (state.selection.$from && hrefs?.length) { const nbef = findStartOfMark(state.selection.$from, view, findLinkMark); const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef; nbef && naft && LinkDocPreview.SetLinkInfo({ |
