diff options
Diffstat (limited to 'src/client/views')
75 files changed, 1196 insertions, 833 deletions
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index b9772fd57..2e6ea1faa 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -20,10 +20,10 @@ export interface DocComponentProps { LayoutTemplate?: () => Opt<Doc>; LayoutTemplateString?: string; } -export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) { +export function DocComponent<P extends DocComponentProps>() { class Component extends Touchable<P> { //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get Document() { return this.props.Document; } // This is the "The Document" -- it encapsulates, data, layout, and any templates @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info @@ -48,7 +48,7 @@ interface ViewBoxBaseProps { renderDepth: number; rootSelected: (outsideReaction?: boolean) => boolean; } -export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor: (doc: Doc) => T) { +export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() { class Component extends Touchable<P> { //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then //@computed get Document(): T { return schemaCtor(this.props.Document); } @@ -92,17 +92,17 @@ export interface ViewBoxAnnotatableProps { renderDepth: number; isAnnotationOverlay?: boolean; } -export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) { +export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() { class Component extends Touchable<P> { @observable _annotationKeySuffix = () => "annotations"; @observable _isAnyChildContentActive = false; //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get Document() { return this.props.Document; } // This is the "The Document" -- it encapsulates, data, layout, and any templates @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info - @computed get layoutDoc() { return schemaCtor(Doc.Layout(this.props.Document)); } + @computed get layoutDoc() { return Doc.Layout(this.props.Document); } // This is the data part of a document -- ie, the data that is constant across all views of the document @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cb95a1237..657d92b8a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -22,13 +22,13 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; import { KeyManager } from './GlobalKeyHandler'; +import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; import { DocumentView } from "./nodes/DocumentView"; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import React = require("react"); -import { InkingStroke } from './InkingStroke'; -import e = require('express'); +import { CollectionFreeFormView } from './collections/collectionFreeForm'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> { @@ -148,7 +148,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P pageY: e.pageY, preventDefault: emptyFunction, button: 0 - }, [SelectionManager.Views().lastElement().rootDoc]); + }, [SelectionManager.Views().slice(-1)[0].rootDoc]); return true; }, emptyFunction, this.onMaximizeClick, false, false); } @@ -233,7 +233,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P this._resizeUndo = UndoManager.StartBatch("DocDecs resize"); this._snapX = e.pageX; this._snapY = e.pageY; - SelectionManager.Views().forEach(docView => this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) })); + const ffviewSet = new Set<CollectionFreeFormView>(); + SelectionManager.Views().forEach(docView => { + const ffview = docView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + ffview && ffviewSet.add(ffview); + this._dragHeights.set(docView.layoutDoc, { start: NumCast(docView.rootDoc._height), lowest: NumCast(docView.rootDoc._height) }); + }); + Array.from(ffviewSet).map(ffview => ffview.setupDragLines(false)); } onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { @@ -395,10 +401,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] })) .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => { Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth) - ({ - X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width, - Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height - }))); + ({ + X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width, + Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height + }))); Doc.SetNativeWidth(doc, undefined); Doc.SetNativeHeight(doc, undefined); }); @@ -424,7 +430,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P render() { const bounds = this.Bounds; - const seldoc = SelectionManager.Views().lastElement(); + const seldoc = SelectionManager.Views().slice(-1)[0]; if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 04abdbf37..50dca0a99 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -13,7 +13,7 @@ import { CognitiveServices } from "../cognitive_services/CognitiveServices"; import { DocUtils } from "../documents/Documents"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { InteractionUtils } from "../util/InteractionUtils"; -import { Scripting } from "../util/Scripting"; +import { ScriptingGlobals } from "../util/ScriptingGlobals"; import { SelectionManager } from "../util/SelectionManager"; import { Transform } from "../util/Transform"; import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu"; @@ -943,11 +943,11 @@ export enum ToolglassTools { None = "none", } -Scripting.addGlobal("GestureOverlay", GestureOverlay); -Scripting.addGlobal(function setToolglass(tool: any) { +ScriptingGlobals.add("GestureOverlay", GestureOverlay); +ScriptingGlobals.add(function setToolglass(tool: any) { runInAction(() => GestureOverlay.Instance.Tool = tool); }); -Scripting.addGlobal(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) { +ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) { runInAction(() => { GestureOverlay.Instance.SavedColor = ActiveInkColor(); SetActiveInkColor(color); @@ -959,12 +959,12 @@ Scripting.addGlobal(function setPen(width: any, color: any, fill: any, arrowStar SetActiveDash(dash); }); }); -Scripting.addGlobal(function resetPen() { +ScriptingGlobals.add(function resetPen() { runInAction(() => { SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)"); SetActiveInkWidth(GestureOverlay.Instance.SavedWidth?.toString() ?? "2"); }); }, "resets the pen tool"); -Scripting.addGlobal(function createText(text: any, x: any, y: any) { +ScriptingGlobals.add(function createText(text: any, x: any, y: any) { GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text); }, "creates a text document with inputted text and coordinates", "(text: any, x: any, y: any)"); diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 24f796105..a7f511ab4 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -51,6 +51,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number")); const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex; + if (!wasSelected) InkStrokeProperties.Instance._currentPoint = -1; const origInk = this.props.inkCtrlPoints.slice(); setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 9dbd97c16..40fe6aa68 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -24,9 +24,7 @@ import React = require("react"); import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, WidthSym } from "../../fields/Doc"; -import { documentSchema } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool } from "../../fields/InkField"; -import { makeInterface } from "../../fields/Schema"; import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; import { OmitKeys, returnFalse, setupMoveUpEvents } from "../../Utils"; @@ -46,11 +44,8 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView"; import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; import Color = require("color"); -type InkDocument = makeInterface<[typeof documentSchema]>; -const InkDocument = makeInterface(documentSchema); - @observer -export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) { +export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big) public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } public static IsClosed(inkData: InkData) { diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 7553c8118..8560ccb29 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,13 +1,15 @@ -import { MainView } from "./MainView"; -import { Docs } from "../documents/Documents"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import * as ReactDOM from 'react-dom'; +// if ((module as any).hot) { +// (module as any).hot.accept(); +// } + import * as React from 'react'; -import { DocServer } from "../DocServer"; +import * as ReactDOM from 'react-dom'; import { AssignAllExtensions } from "../../extensions/General/Extensions"; -import { Networking } from "../Network"; -import { CollectionView } from "./collections/CollectionView"; +import { Docs } from "../documents/Documents"; +import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { LinkManager } from "../util/LinkManager"; +import { CollectionView } from "./collections/CollectionView"; +import { MainView } from "./MainView"; AssignAllExtensions(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index fc1c694a2..8c0795881 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -5,7 +5,6 @@ import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import "normalize.css"; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; @@ -24,7 +23,7 @@ import { DocumentManager } from '../util/DocumentManager'; import { GroupManager } from '../util/GroupManager'; import { HistoryUtil } from '../util/History'; import { Hypothesis } from '../util/HypothesisUtils'; -import { Scripting } from '../util/Scripting'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SelectionManager } from '../util/SelectionManager'; import { ColorScheme, SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; @@ -45,7 +44,6 @@ import { GestureOverlay } from './GestureOverlay'; import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; import { Colors } from './global/globalEnums'; import { KeyManager } from './GlobalKeyHandler'; -import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; import "./MainView.scss"; @@ -165,28 +163,28 @@ export class MainView extends React.Component { } } - library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar, - fa.faSquare, far.faSquare as any, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, - fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, - fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight, - fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow, - fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft, - fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp, - fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt, - fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer, - fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV, - fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, - fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined, - fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, - fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper as any, - fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle as any, - fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer as any, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp, - fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl, - fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo, - fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical, - fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll, - fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines, - fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faSearchPlus, fa.faVolumeUp, fa.faVolumeMute, fa.faMapMarkerAlt); + library.add(...[fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar, + fa.faSquare, far.faSquare as any, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, + fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, + fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight, + fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow, + fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft, + fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp, + fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt, + fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer, + fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV, + fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, + fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined, + fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, + fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper as any, + fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle as any, + fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer as any, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp, + fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl, + fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo, + fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical, + fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll, + fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines, + fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt]); this.initAuthenticationRouters(); } @@ -696,4 +694,4 @@ export class MainView extends React.Component { } } -Scripting.addGlobal(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); });
\ No newline at end of file +ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); });
\ No newline at end of file diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 7cf388872..0f51cf9b2 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -5,17 +5,17 @@ import ReactLoading from 'react-loading'; import { Doc } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { Cast, NumCast } from "../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils, emptyPath } from "../../Utils"; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, setupMoveUpEvents, Utils } from "../../Utils"; +import { DocUtils } from "../documents/Documents"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { DragManager } from "../util/DragManager"; -import { Scripting } from "../util/Scripting"; +import { ScriptingGlobals } from "../util/ScriptingGlobals"; import { Transform } from "../util/Transform"; import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView"; import { DocumentView } from "./nodes/DocumentView"; import './OverlayView.scss'; import { ScriptingRepl } from './ScriptingRepl'; import { DefaultStyleProvider } from "./StyleProvider"; -import { DocUtils } from "../documents/Documents"; export type OverlayDisposer = () => void; @@ -226,6 +226,6 @@ export class OverlayView extends React.Component { } } // bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error -Scripting.addGlobal(function addOverlayWindow(type: string, options: OverlayElementOptions) { +ScriptingGlobals.add(function addOverlayWindow(type: string, options: OverlayElementOptions) { OverlayView.Instance.addWindow(<ScriptingRepl />, options); });
\ No newline at end of file diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index f9dab9f82..dd6448654 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -18,6 +18,10 @@ import React = require("react"); import { Colors } from "./global/globalEnums"; import { CollectionFreeFormView } from "./collections/collectionFreeForm"; import { DocumentManager } from "../util/DocumentManager"; +import { pasteImageBitmap } from "./nodes/WebBoxRenderer"; +import { VideoBox } from "./nodes/VideoBox"; +import { Id } from "../../fields/FieldSymbols"; +import { ImageField } from "../../fields/URLField"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -92,6 +96,20 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get groupButton() { return this.propertyToggleBtn("Group", "isGroup", on => `Display collection as a Group`, on => "object-group", (dv, doc) => { doc.isGroup = !doc.isGroup; doc.forceActive = doc.isGroup; }); } + @computed get freezeThumb() { + return this.propertyToggleBtn("Freeze\Thumb", "_thumb-frozen", on => `${on ? "Freeze" : "Unfreeze"} thumbnail`, on => "arrows-alt-h", (dv, doc) => { + if (doc["thumb-frozen"]) doc["thumb-frozen"] = undefined; + else { + document.body.focus(); // so that we can access the clipboard without an error + setTimeout(() => + pasteImageBitmap((data: any, error: any) => { + error && console.log(error); + data && VideoBox.convertDataUri(data, doc[Id] + "-thumb-frozen", true).then( + returnedfilename => doc["thumb-frozen"] = new ImageField(returnedfilename)); + })); + } + }); + } @computed get snapButton() { return this.propertyToggleBtn("Snap\xA0Lines", "showSnapLines", on => `Display snapping lines when objects are dragged`, on => "border-all", undefined, true); } @@ -222,6 +240,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {toggle(this.dictationButton, { display: isNovice ? "none" : "" })} {toggle(this.onClickButton)} {toggle(this.fitWidthButton)} + {toggle(this.freezeThumb)} {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? "none" : "" })} {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? "none" : "" })} {toggle(this.maskButton, { display: !isInk ? "none" : "" })} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index f53944805..47a5cd07e 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -33,6 +33,7 @@ import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector"; import "./PropertiesView.scss"; import { DefaultStyleProvider } from "./StyleProvider"; import { PresBox } from "./nodes/trails"; +import { IconLookup } from "@fortawesome/fontawesome-svg-core"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -1301,7 +1302,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { onPointerDown={this.toggleAnchor} onClick={e => e.stopPropagation()} className="propertiesButton" > - <FontAwesomeIcon className="fa-icon" icon={faAnchor} size="lg" /> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> </button> </div> <div className="propertiesView-input inline" id="propertiesView-displayArrow"> @@ -1311,7 +1312,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { onPointerDown={this.toggleArrow} onClick={e => e.stopPropagation()} className="propertiesButton" > - <FontAwesomeIcon className="fa-icon" icon={faArrowRight} size="lg" /> + <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" /> </button> </div> </div> diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index e2b5d8dc3..b7ea124b9 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -1,17 +1,17 @@ -import * as React from "react"; +import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import { observable, action } from "mobx"; - -import "./ScriptBox.scss"; -import { OverlayView } from "./OverlayView"; -import { DocumentIconContainer } from "./nodes/DocumentIcon"; -import { Opt, Doc } from "../../fields/Doc"; -import { emptyFunction } from "../../Utils"; -import { ScriptCast } from "../../fields/Types"; -import { CompileScript } from "../util/Scripting"; +import * as React from "react"; +import { Doc, Opt } from "../../fields/Doc"; import { ScriptField } from "../../fields/ScriptField"; +import { ScriptCast } from "../../fields/Types"; +import { emptyFunction } from "../../Utils"; import { DragManager } from "../util/DragManager"; +import { CompileScript } from "../util/Scripting"; import { EditableView } from "./EditableView"; +import { DocumentIconContainer } from "./nodes/DocumentIcon"; +import { OverlayView } from "./OverlayView"; +import "./ScriptBox.scss"; + export interface ScriptBoxProps { onSave: (text: string, onError: (error: string) => void) => void; diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx index c598b2861..4ed6da24a 100644 --- a/src/client/views/ScriptingRepl.tsx +++ b/src/client/views/ScriptingRepl.tsx @@ -3,7 +3,8 @@ import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DocumentManager } from '../util/DocumentManager'; -import { CompileScript, Scripting, Transformer, ts } from '../util/Scripting'; +import { CompileScript, Transformer, ts } from '../util/Scripting'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { DocumentIconContainer } from './nodes/DocumentIcon'; import { OverlayView } from './OverlayView'; import './ScriptingRepl.scss'; @@ -81,7 +82,7 @@ export class ScriptingRepl extends React.Component { transformer: context => { const knownVars: { [name: string]: number } = {}; const usedDocuments: number[] = []; - Scripting.getGlobals().forEach(global => knownVars[global] = 1); + ScriptingGlobals.getGlobals().forEach((global: any) => knownVars[global] = 1); return root => { function visit(node: ts.Node) { let skip = false; @@ -124,7 +125,7 @@ export class ScriptingRepl extends React.Component { case "Enter": { const docGlobals: { [name: string]: any } = {}; DocumentManager.Instance.DocumentViews.forEach((dv, i) => docGlobals[`d${i}`] = dv.props.Document); - const globals = Scripting.makeMutableGlobalsCopy(docGlobals); + const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals); const script = CompileScript(this.commandString, { typecheck: false, addReturn: true, editable: true, params: { args: "any" }, transformer: this.getTransformer(), globals }); if (!script.compiled) { this.commands.push({ command: this.commandString, result: script.errors }); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index ce363cc47..2782574c5 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -7,7 +7,7 @@ import { Doc, Opt, StrListCast } from "../../fields/Doc"; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; -import { lightOrDark, DashColor } from '../../Utils'; +import { DashColor, lightOrDark } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { ColorScheme } from '../util/SettingsManager'; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index ff3f92364..b3a24e031 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -5,9 +5,9 @@ import { List } from "../../fields/List"; import { ScriptField } from "../../fields/ScriptField"; import { Cast, StrCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../Utils"; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; -import { Scripting } from "../util/Scripting"; +import { ScriptingGlobals } from "../util/ScriptingGlobals"; import { Transform } from "../util/Transform"; import { undoBatch } from "../util/UndoManager"; import { CollectionTreeView } from "./collections/CollectionTreeView"; @@ -164,7 +164,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { } } -Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) { +ScriptingGlobals.add(function switchView(doc: Doc, template: Doc | undefined) { if (template?.dragFactory) { template = Cast(template.dragFactory, Doc, null); } diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index 82b0218bf..92d3e2bed 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -1,18 +1,17 @@ -import * as React from "react"; -import "./Keyframe.scss"; -import "./Timeline.scss"; -import "../global/globalCssVariables.scss"; +import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { observable, reaction, action, IReactionDisposer, observe, computed, runInAction, trace } from "mobx"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; -import { Cast, NumCast } from "../../../fields/Types"; +import * as React from "react"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { List } from "../../../fields/List"; -import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../fields/Schema"; -import { Transform } from "../../util/Transform"; -import { TimelineMenu } from "./TimelineMenu"; +import { createSchema, defaultSpec, listSpec, makeInterface } from "../../../fields/Schema"; +import { Cast, NumCast } from "../../../fields/Types"; import { Docs } from "../../documents/Documents"; +import { Transform } from "../../util/Transform"; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { emptyPath } from "../../../Utils"; +import "../global/globalCssVariables.scss"; +import "./Keyframe.scss"; +import "./Timeline.scss"; +import { TimelineMenu } from "./TimelineMenu"; /** diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index c7e62c15d..e80ba6f36 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -13,6 +13,7 @@ import { TimelineOverview } from "./TimelineOverview"; import { Track } from "./Track"; import clamp from "../../util/clamp"; import { DocumentType } from "../../documents/DocumentTypes"; +import { IconLookup } from "@fortawesome/fontawesome-svg-core"; /** * Timeline class controls most of timeline functions besides individual keyframe and track mechanism. Main functions are @@ -346,9 +347,9 @@ export class Timeline extends React.Component<FieldViewProps> { return ( <div key="timeline_toolbox" className="timeline-toolbox" style={{ height: `${size}px` }}> <div className="playbackControls"> - <div className="timeline-icon" key="timeline_windBack" onClick={this.windBackward} title="Slow Down Animation"> <FontAwesomeIcon icon={faBackward} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> - <div className="timeline-icon" key=" timeline_play" onClick={this.onPlay} title="Play/Pause"> <FontAwesomeIcon icon={this._playButton} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> - <div className="timeline-icon" key="timeline_windForward" onClick={this.windForward} title="Speed Up Animation"> <FontAwesomeIcon icon={faForward} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> + <div className="timeline-icon" key="timeline_windBack" onClick={this.windBackward} title="Slow Down Animation"> <FontAwesomeIcon icon={faBackward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> + <div className="timeline-icon" key=" timeline_play" onClick={this.onPlay} title="Play/Pause"> <FontAwesomeIcon icon={this._playButton as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> + <div className="timeline-icon" key="timeline_windForward" onClick={this.windForward} title="Speed Up Animation"> <FontAwesomeIcon icon={faForward as IconLookup} style={{ height: `${iconSize}px`, width: `${iconSize}px` }} /> </div> </div> <div className="grid-box overview-tool"> <div className="overview-box"> @@ -506,7 +507,7 @@ export class Timeline extends React.Component<FieldViewProps> { {this.children.map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)} </div> <div key="timeline_resize" onPointerDown={this.onResizeDown}> - <FontAwesomeIcon className="resize" icon={faGripLines} /> + <FontAwesomeIcon className="resize" icon={faGripLines as IconLookup} /> </div> </div> </div> diff --git a/src/client/views/animationtimeline/TimelineMenu.tsx b/src/client/views/animationtimeline/TimelineMenu.tsx index 53ca9acad..aa422c092 100644 --- a/src/client/views/animationtimeline/TimelineMenu.tsx +++ b/src/client/views/animationtimeline/TimelineMenu.tsx @@ -5,6 +5,7 @@ import "./TimelineMenu.scss"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChartLine, faRoad, faClipboard, faPen, faTrash, faTable } from "@fortawesome/free-solid-svg-icons"; import { Utils } from "../../../Utils"; +import { IconLookup } from "@fortawesome/fontawesome-svg-core"; @observer @@ -41,7 +42,7 @@ export class TimelineMenu extends React.Component { if (type === "input") { const inputRef = React.createRef<HTMLInputElement>(); let text = ""; - this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faClipboard} size="lg" /><input className="timeline-menu-input" ref={inputRef} placeholder={title} onChange={(e) => { + this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faClipboard as IconLookup} size="lg" /><input className="timeline-menu-input" ref={inputRef} placeholder={title} onChange={(e) => { e.stopPropagation(); text = e.target.value; }} onKeyDown={(e) => { @@ -52,7 +53,7 @@ export class TimelineMenu extends React.Component { } }} /></div>); } else if (type === "button") { - this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faChartLine} size="lg" /><p className="timeline-menu-desc" onClick={(e) => { + this._currentMenu.push(<div key={Utils.GenerateGuid()} className="timeline-menu-item"><FontAwesomeIcon icon={faChartLine as IconLookup} size="lg" /><p className="timeline-menu-desc" onClick={(e) => { e.preventDefault(); e.stopPropagation(); event(e); diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 3c66faf0c..01f41869e 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -3,23 +3,17 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../../fields/Doc'; -import { collectionSchema, documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; -import { makeInterface } from '../../../fields/Schema'; -import { ScriptField } from '../../../fields/ScriptField'; import { NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { OmitKeys, returnFalse, Utils } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { DocumentView } from '../nodes/DocumentView'; +import { StyleProp } from '../StyleProvider'; import "./CollectionCarousel3DView.scss"; import { CollectionSubView } from './CollectionSubView'; -import { StyleProp } from '../StyleProvider'; - -type Carousel3DDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>; -const Carousel3DDocument = makeInterface(documentSchema, collectionSchema); @observer -export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocument) { +export class CollectionCarousel3DView extends CollectionSubView() { @computed get scrollSpeed() { return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed } @@ -28,7 +22,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume componentWillUnmount() { this._dropDisposer?.(); } - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view this._dropDisposer?.(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 6c2c27e8e..467c2893f 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -3,8 +3,6 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, Opt } from '../../../fields/Doc'; -import { collectionSchema, documentSchema } from '../../../fields/documentSchemas'; -import { makeInterface } from '../../../fields/Schema'; import { NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { OmitKeys, returnFalse } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; @@ -12,18 +10,15 @@ import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import "./CollectionCarouselView.scss"; -import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; - -type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>; -const CarouselDocument = makeInterface(documentSchema, collectionSchema); +import { CollectionSubView } from './CollectionSubView'; @observer -export class CollectionCarouselView extends CollectionSubView(CarouselDocument) { +export class CollectionCarouselView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; componentWillUnmount() { this._dropDisposer?.(); } - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view this._dropDisposer?.(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index f543d924d..9e8374605 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -4,31 +4,31 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from "mo import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import * as GoldenLayout from "../../../client/goldenLayout"; -import { Doc, DocListCast, Opt, DocListCastAsync, DataSym } from "../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { inheritParentAcls } from '../../../fields/util'; import { DocServer } from "../../DocServer"; import { Docs } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DragManager } from "../../util/DragManager"; import { InteractionUtils } from '../../util/InteractionUtils'; -import { Scripting } from '../../util/Scripting'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { LightboxView } from '../LightboxView'; import "./CollectionDockingView.scss"; import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; import { CollectionViewType } from './CollectionView'; import { TabDocView } from './TabDocView'; import React = require("react"); -import { DocumentType } from '../../documents/DocumentTypes'; -import { listSpec } from '../../../fields/Schema'; -import { LightboxView } from '../LightboxView'; -import { inheritParentAcls } from '../../../fields/util'; const _global = (window /* browser */ || global /* node */) as any; @observer -export class CollectionDockingView extends CollectionSubView(doc => doc) { +export class CollectionDockingView extends CollectionSubView() { @observable public static Instance: CollectionDockingView; public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) { return { @@ -468,8 +468,8 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { } } -Scripting.addGlobal(function openInLightbox(doc: any) { LightboxView.AddDocTab(doc, "lightbox"); }, +ScriptingGlobals.add(function openInLightbox(doc: any) { LightboxView.AddDocTab(doc, "lightbox"); }, "opens up document in a lightbox", "(doc: any)"); -Scripting.addGlobal(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); }, +ScriptingGlobals.add(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); }, "opens up document in tab on right side of the screen", "(doc: any)"); -Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
\ No newline at end of file +ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); });
\ No newline at end of file diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 131f5ba46..364a2440e 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -20,7 +20,7 @@ import { Docs } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { DragManager } from "../../util/DragManager"; -import { Scripting } from "../../util/Scripting"; +import { ScriptingGlobals } from "../../util/ScriptingGlobals"; import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; @@ -1285,7 +1285,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewMenu ); } } -Scripting.addGlobal(function gotoFrame(doc: any, newFrame: any) { +ScriptingGlobals.add(function gotoFrame(doc: any, newFrame: any) { const dataField = doc[Doc.LayoutFieldKey(doc)]; const childDocs = DocListCast(dataField); const currentFrame = Cast(doc._currentFrame, "number", null); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index bc1407c53..0a336c544 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -13,7 +13,7 @@ import { CollectionSubView } from "./CollectionSubView"; import React = require("react"); @observer -export class CollectionPileView extends CollectionSubView(doc => doc) { +export class CollectionPileView extends CollectionSubView() { _originalChrome: any = ""; _disposers: { [name: string]: IReactionDisposer } = {}; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 7d9dc39ae..61ddbe70d 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -11,7 +11,7 @@ import { computedFn } from "mobx-utils"; import { Doc, DocListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; -import { listSpec, makeInterface } from "../../../fields/Schema"; +import { listSpec } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; import { @@ -25,7 +25,7 @@ import { Docs } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import { LinkManager } from "../../util/LinkManager"; -import { Scripting } from "../../util/Scripting"; +import { ScriptingGlobals } from "../../util/ScriptingGlobals"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; @@ -43,8 +43,6 @@ import { import { LabelBox } from "../nodes/LabelBox"; import "./CollectionStackedTimeline.scss"; -type PanZoomDocument = makeInterface<[]>; -const PanZoomDocument = makeInterface(); export type CollectionStackedTimelineProps = { Play: () => void; Pause: () => void; @@ -67,11 +65,8 @@ export enum TrimScope { } @observer -export class CollectionStackedTimeline extends CollectionSubView< - PanZoomDocument, - CollectionStackedTimelineProps ->(PanZoomDocument) { - @observable static SelectingRegion: CollectionStackedTimeline | undefined; +export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() { + @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; @observable public static CurrentlyPlaying: Doc[]; static RangeScript: ScriptField; @@ -933,7 +928,9 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> <div key="right" className="collectionStackedTimeline-resizer" - onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, false)} + onPointerDown={(e) => + this.onAnchorDown(e, this.props.mark, false) + } /> </> )} @@ -941,12 +938,12 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> ); } } -Scripting.addGlobal(function formatToTime(time: number): any { +ScriptingGlobals.add(function formatToTime(time: number): any { return formatTime(time); }); -Scripting.addGlobal(function min(num1: number, num2: number): number { +ScriptingGlobals.add(function min(num1: number, num2: number): number { return Math.min(num1, num2); }); -Scripting.addGlobal(function max(num1: number, num2: number): number { +ScriptingGlobals.add(function max(num1: number, num2: number): number { return Math.max(num1, num2); });
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index cdc680a08..8634ea139 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -3,16 +3,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { CursorProperty } from "csstype"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../fields/Doc"; -import { collectionSchema, documentSchema } from "../../../fields/documentSchemas"; +import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; -import { listSpec, makeInterface } from "../../../fields/Schema"; +import { listSpec } from "../../../fields/Schema"; import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils, returnTrue, returnEmptyDoclist, returnEmptyFilter } from "../../../Utils"; -import { DocUtils, Docs } from "../../documents/Documents"; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; +import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager, dropActionType } from "../../util/DragManager"; import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; @@ -23,18 +22,14 @@ import { EditableView } from "../EditableView"; import { LightboxView } from "../LightboxView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView"; -import { StyleProp, DefaultStyleProvider } from "../StyleProvider"; +import { StyleProp } from "../StyleProvider"; import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; -import { FontIconBox } from "../nodes/button/FontIconBox"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; const _global = (window /* browser */ || global /* node */) as any; -type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; -const StackingDocument = makeInterface(collectionSchema, documentSchema); export type collectionStackingViewProps = { chromeHidden?: boolean; @@ -44,7 +39,7 @@ export type collectionStackingViewProps = { }; @observer -export class CollectionStackingView extends CollectionSubView<StackingDocument, Partial<collectionStackingViewProps>>(StackingDocument) { +export class CollectionStackingView extends CollectionSubView<Partial<collectionStackingViewProps>>() { _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef<HTMLDivElement>(); _pivotFieldDisposer?: IReactionDisposer; diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx index c5c3f96e8..c025e94a8 100644 --- a/src/client/views/collections/CollectionStaffView.tsx +++ b/src/client/views/collections/CollectionStaffView.tsx @@ -6,7 +6,7 @@ import "./CollectionStaffView.scss"; import { observer } from "mobx-react"; @observer -export class CollectionStaffView extends CollectionSubView(doc => doc) { +export class CollectionStaffView extends CollectionSubView() { private _reactionDisposer: IReactionDisposer | undefined; @observable private _staves = NumCast(this.props.Document.staves); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 34209ebc9..42e157396 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -10,14 +10,14 @@ import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils"; import { DocServer } from "../../DocServer"; -import { Networking } from "../../Network"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; import { InteractionUtils } from "../../util/InteractionUtils"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; import React = require("react"); -import * as rp from 'request-promise'; import ReactLoading from 'react-loading'; +import * as rp from 'request-promise'; +import { Networking } from "../../Network"; export interface SubCollectionViewProps extends CollectionViewProps { @@ -25,15 +25,15 @@ export interface SubCollectionViewProps extends CollectionViewProps { isAnyChildContentActive: () => boolean; } -export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: X) { - class CollectionSubView extends DocComponent<X & SubCollectionViewProps, T>(schemaCtor) { +export function CollectionSubView<X>(moreProps?: X) { + class CollectionSubView extends DocComponent<X & SubCollectionViewProps>() { private dropDisposer?: DragManager.DragDropDisposer; private gestureDisposer?: GestureUtils.GestureEventDisposer; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; protected _mainCont?: HTMLDivElement; @observable _focusFilters: Opt<string[]>; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it @observable _focusRangeFilters: Opt<string[]>; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view this.dropDisposer?.(); this.gestureDisposer?.(); this._multiTouchDisposer?.(); diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 292dfd77c..d6398fda5 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,17 +1,17 @@ import { toUpper } from "lodash"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocCastAsync, Opt, StrListCast } from "../../../fields/Doc"; +import { Doc, Opt, StrListCast } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { ObjectField } from "../../../fields/ObjectField"; import { RichTextField } from "../../../fields/RichTextField"; import { listSpec } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, returnEmptyString } from "../../../Utils"; -import { Docs, DocUtils } from "../../documents/Documents"; +import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; import { DocumentManager } from "../../util/DocumentManager"; -import { Scripting } from "../../util/Scripting"; +import { ScriptingGlobals } from "../../util/ScriptingGlobals"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; @@ -20,13 +20,10 @@ import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngi import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTimeView.scss"; -const higflyout = require("@hig/flyout"); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; import React = require("react"); @observer -export class CollectionTimeView extends CollectionSubView(doc => doc) { +export class CollectionTimeView extends CollectionSubView() { _changing = false; @observable _layoutEngine = "pivot"; @observable _collapsed: boolean = false; @@ -236,7 +233,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { } } -Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { +ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) { let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex); pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField); pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ea077ea40..e84517f40 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -3,11 +3,11 @@ import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; -import { Document, listSpec } from '../../../fields/Schema'; +import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnOne } from '../../../Utils'; +import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from '../../util/DocumentManager'; @@ -40,7 +40,7 @@ export type collectionTreeViewProps = { }; @observer -export class CollectionTreeView extends CollectionSubView<Document, Partial<collectionTreeViewProps>>(Document) { +export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() { private _treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; private _titleRef?: HTMLDivElement | HTMLInputElement | null; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 681a15e3d..ee2c28b5f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,15 +1,14 @@ -import { computed, observable, runInAction, action } from 'mobx'; +import { computed, observable, runInAction } from 'mobx'; import { observer } from "mobx-react"; import * as React from 'react'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app -import { Doc, DocListCast, StrListCast } from '../../../fields/Doc'; -import { documentSchema } from '../../../fields/documentSchemas'; +import { Doc, DocListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { ObjectField } from '../../../fields/ObjectField'; -import { makeInterface } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; +import { returnEmptyString } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { BranchCreate, BranchTask } from '../../documents/Gitlike'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; @@ -33,10 +32,7 @@ import { CollectionStackingView } from './CollectionStackingView'; import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import './CollectionView.scss'; -import { returnEmptyString } from '../../../Utils'; -import { InkTool } from '../../../fields/InkField'; export const COLLECTION_BORDER_WIDTH = 2; const path = require('path'); @@ -84,11 +80,8 @@ export interface CollectionViewProps extends FieldViewProps { childClickScript?: ScriptField; childDoubleClickScript?: ScriptField; } - -type CollectionDocument = makeInterface<[typeof documentSchema]>; -const CollectionDocument = makeInterface(documentSchema); @observer -export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps, CollectionDocument>(CollectionDocument) { +export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps>() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } @observable private static _safeMode = false; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index aeda71d01..e2ea81392 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -4,13 +4,12 @@ import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { DateField } from "../../../../fields/DateField"; import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; -import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { ObjectField } from "../../../../fields/ObjectField"; import { RichTextField } from "../../../../fields/RichTextField"; -import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema"; +import { createSchema, listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { TraceMobx } from "../../../../fields/util"; @@ -41,7 +40,6 @@ import { LightboxView } from "../../LightboxView"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; -import { pageSchema } from "../../nodes/ImageBox"; import { PresBox } from "../../nodes/trails/PresBox"; import { StyleLayers, StyleProp } from "../../StyleProvider"; import { CollectionDockingView } from "../CollectionDockingView"; @@ -67,8 +65,6 @@ export const panZoomSchema = createSchema({ scrollHeight: "number" // this will be set when the collection is an annotation overlay for a PDF/Webpage }); -type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>; -const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema); export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) viewDefDivClick?: ScriptField; @@ -81,7 +77,7 @@ export type collectionFreeformViewProps = { }; @observer -export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, Partial<collectionFreeformViewProps>>(PanZoomDocument) { +export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() { public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive private _lastNudge: any; @@ -95,6 +91,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P private _clusterDistance: number = 75; private _hitCluster: number = -1; private _disposers: { [name: string]: IReactionDisposer } = {}; + private _renderCutoffData = observable.map<string, boolean>(); private _layoutPoolData = observable.map<string, PoolData>(); private _layoutSizeData = observable.map<string, { width?: number, height?: number }>(); private _cachedPool: Map<string, PoolData> = new Map(); @@ -109,6 +106,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0 @observable _hLines: number[] | undefined; @observable _vLines: number[] | undefined; + @observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. @observable _pullCoords: number[] = [0, 0]; @observable _pullDirection: string = ""; @observable _showAnimTimeline = false; @@ -163,7 +161,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.layoutDoc._panY = vals.bounds.cy; this.layoutDoc._viewScale = vals.scale; } - freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined; + freeformData = (force?: boolean) => !this._firstRender && (this.fitToContent || force) ? this.fitToContentVals : undefined; reverseNativeScaling = () => this.fitToContent ? true : false; panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY); @@ -205,7 +203,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) { - CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document._currentFrame, true); + CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true); } } return retVal; @@ -235,12 +233,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const d = docDragData.droppedDocuments[i]; const layoutDoc = Doc.Layout(d); if (this.Document._currentFrame !== undefined) { - CollectionFreeFormDocumentView.setupKeyframes([d], this.Document._currentFrame, false); + CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false); const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); vals.x = x + (vals.x || 0) - dropPos[0]; vals.y = y + (vals.y || 0) - dropPos[1]; vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined; - CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, vals); + CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals); } else { d.x = x + NumCast(d.x) - dropPos[0]; d.y = y + NumCast(d.y) - dropPos[1]; @@ -635,7 +633,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @action pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => { const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, 0, true); + this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true); this._lastX = e.clientX; this._lastY = e.clientY; } @@ -967,10 +965,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P .filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! })); if (measuredDocs.length) { const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) => // computes range of content - ({ - xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) }, - yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) } - }) + ({ + xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) }, + yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) } + }) , { xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }, yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE } @@ -1045,11 +1043,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (state.type === "doc" && this.Document._panX !== undefined && this.Document._panY !== undefined) { const init = state.initializers![this.Document[Id]]; if (!init) { - state.initializers![this.Document[Id]] = { panX: this.Document._panX, panY: this.Document._panY }; + state.initializers![this.Document[Id]] = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY) }; HistoryUtil.pushState(state); } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) { - init.panX = this.Document._panX; - init.panY = this.Document._panY; + init.panX = NumCast(this.Document._panX); + init.panY = NumCast(this.Document._panY); HistoryUtil.pushState(state); } } @@ -1144,7 +1142,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - getChildDocView(entry: PoolData) { + getChildDocView(entry: PoolData, renderIndex: number) { const childLayout = entry.pair.layout; const childData = entry.pair.data; const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); @@ -1153,6 +1151,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P Document={childLayout} renderDepth={this.props.renderDepth + 1} replica={entry.replica} + renderIndex={renderIndex} + renderCutoffProvider={this.renderCutoffProvider} ContainingCollectionView={this.props.CollectionView} ContainingCollectionDoc={this.props.Document} CollectionFreeFormView={this} @@ -1213,7 +1213,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const { z, color, zIndex } = params.pair.layout; const { x, y, opacity } = this.Document._currentFrame === undefined ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } : - CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document._currentFrame); + CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame)); return { x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"), transition: StrCast(layoutDoc.dataTransition), opacity: this._keyframeEditing ? 1 : Cast(opacity, "number", null), @@ -1256,6 +1256,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } + + renderCutoffProvider = computedFn(function renderCutoffProvider(this: any, doc: Doc) { + return !this._renderCutoffData.get(doc[Id] + ""); + }.bind(this)); + + childPositionProviderUnmemoized = (doc: Doc, replica: string) => { return this._layoutPoolData.get(doc[Id] + (replica || "")); } @@ -1300,6 +1306,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } + @observable _numLoaded = 1; get doLayoutComputation() { const { newPool, computedElementData } = this.doInternalLayoutComputation; const array = Array.from(newPool.entries()); @@ -1318,9 +1325,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this._cachedPool.clear(); Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1])); const elements = computedElementData.slice(); - Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry => + Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach((entry, i) => elements.push({ - ele: this.getChildDocView(entry[1]), + ele: this.getChildDocView(entry[1], i), bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) })); @@ -1369,35 +1376,38 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P componentDidMount() { super.componentDidMount?.(); this.props.setContentView?.(this); - this._disposers.layoutComputation = reaction(() => this.doLayoutComputation, - (elements) => this._layoutElements = elements || [], - { fireImmediately: true, name: "doLayout" }); - - this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); - - this._disposers.groupBounds = reaction(() => { - if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) { - const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() })); - return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); - } - return undefined; - }, - (cbounds) => { - if (cbounds) { - const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; - const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; - const pbounds = { - x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1], - r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1] - }; - this.layoutDoc._width = (pbounds.r - pbounds.x); - this.layoutDoc._height = (pbounds.b - pbounds.y); - this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; - this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; - this.layoutDoc.x = pbounds.x; - this.layoutDoc.y = pbounds.y; + setTimeout(action(() => { + this._firstRender = false; + this._disposers.layoutComputation = reaction(() => this.doLayoutComputation, + (elements) => this._layoutElements = elements || [], + { fireImmediately: true, name: "doLayout" }); + + this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); + + this._disposers.groupBounds = reaction(() => { + if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) { + const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() })); + return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); } - }, { fireImmediately: true }); + return undefined; + }, + (cbounds) => { + if (cbounds) { + const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; + const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; + const pbounds = { + x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1], + r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1] + }; + this.layoutDoc._width = (pbounds.r - pbounds.x); + this.layoutDoc._height = (pbounds.b - pbounds.y); + this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; + this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; + this.layoutDoc.x = pbounds.x; + this.layoutDoc.y = pbounds.y; + } + }, { fireImmediately: true }); + })); } componentWillUnmount() { @@ -1449,8 +1459,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20; const dim = Math.ceil(Math.sqrt(docs.length)); docs.forEach((doc, i) => { - doc.x = (this.Document._panX || 0) + (i % dim) * width - width * dim / 2; - doc.y = (this.Document._panY || 0) + Math.floor(i / dim) * height - height * dim / 2; + doc.x = NumCast(this.Document._panX) + (i % dim) * width - width * dim / 2; + doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - height * dim / 2; }); } @@ -1557,58 +1567,35 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P e.stopPropagation(); } + incrementalRender = action(() => { + if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { + const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); + const loadIncrement = 5; + for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) { + this._renderCutoffData.set(unrendered[i][Id] + "", true); + } + } + this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); + }); + children = () => { + this.incrementalRender(); const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; - return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />]; - } - - chooseGridSpace = (gridSpace: number): number => { - if (!this.zoomScaling()) return 50; - const divisions = this.props.PanelWidth() / this.zoomScaling() / gridSpace + 3; - return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10); - } - - @computed get backgroundGrid() { - const gridSpace = this.chooseGridSpace(NumCast(this.layoutDoc["_backgroundGrid-spacing"], 50)); - const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.panX() % gridSpace - gridSpace) * this.zoomScaling(); - const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.panY() % gridSpace - gridSpace) * this.zoomScaling(); - const renderGridSpace = gridSpace * this.zoomScaling(); - const w = this.props.PanelWidth() + 2 * renderGridSpace; - const h = this.props.PanelHeight() + 2 * renderGridSpace; - const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)"; - return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }} - ref={(el) => { - const ctx = el?.getContext('2d'); - if (ctx) { - const Cx = this.cachedCenteringShiftX % renderGridSpace; - const Cy = this.cachedCenteringShiftY % renderGridSpace; - ctx.lineWidth = Math.min(1, Math.max(0.5, this.zoomScaling())); - ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); - ctx.clearRect(0, 0, w, h); - if (ctx) { - ctx.strokeStyle = strokeStyle; - ctx.beginPath(); - for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { - ctx.moveTo(x, Cy - h); - ctx.lineTo(x, Cy + h); - } - for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { - ctx.moveTo(Cx - w, y); - ctx.lineTo(Cx + w, y); - } - ctx.stroke(); - } - } - }} />; + return [ + ...children, + ...this.views, + <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> + ]; } @computed get placeholder() { - return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}> + return <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}> <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span> </div>; } @computed get marqueeView() { + TraceMobx(); return <MarqueeView {...this.props} ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined} @@ -1623,7 +1610,18 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}> - {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)} + {this.layoutDoc._backgroundGridShow ? + <CollectionFreeFormBackgroundGrid + PanelWidth={this.props.PanelWidth} + PanelHeight={this.props.PanelHeight} + panX={this.panX} + panY={this.panY} + zoomScaling={this.zoomScaling} + layoutDoc={this.layoutDoc} + isAnnotationOverlay={this.isAnnotationOverlay} + cachedCenteringShiftX={this.cachedCenteringShiftX} + cachedCenteringShiftY={this.cachedCenteringShiftY} + /> : (null)} <CollectionFreeFormViewPannableContents isAnnotationOverlay={this.isAnnotationOverlay} isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable} @@ -1642,7 +1640,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } @computed get contentScaling() { - if (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent) return 0; + if (this._firstRender || (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent)) return 0; const nw = this.nativeWidth; const nh = this.nativeHeight; const hscale = nh ? this.props.PanelHeight() / nh : 1; @@ -1675,9 +1673,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.backgroundEvents ? "all" : this.props.pointerEvents as any, transform: `scale(${this.contentScaling || 1})`, width: `${100 / (this.contentScaling || 1)}%`, - height: this.isAnnotationOverlay && this.Document.scrollHeight ? this.Document.scrollHeight : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() + height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}> - {this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ? + {this._firstRender || (this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0) ? this.placeholder : this.marqueeView} {this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />} @@ -1854,4 +1852,59 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF {this.zoomProgressivize} </div>; } +} + +interface CollectionFreeFormViewBackgroundGridProps { + panX: () => number; + panY: () => number; + PanelWidth: () => number; + PanelHeight: () => number; + isAnnotationOverlay?: boolean; + zoomScaling: () => number; + layoutDoc: Doc; + cachedCenteringShiftX: number; + cachedCenteringShiftY: number; +} +@observer +class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> { + + + chooseGridSpace = (gridSpace: number): number => { + if (!this.props.zoomScaling()) return 50; + const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace + 3; + return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10); + } + render() { + const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc["_backgroundGrid-spacing"], 50)); + const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.props.panX() % gridSpace - gridSpace) * this.props.zoomScaling(); + const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.props.panY() % gridSpace - gridSpace) * this.props.zoomScaling(); + const renderGridSpace = gridSpace * this.props.zoomScaling(); + const w = this.props.PanelWidth() + 2 * renderGridSpace; + const h = this.props.PanelHeight() + 2 * renderGridSpace; + const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)"; + return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }} + ref={(el) => { + const ctx = el?.getContext('2d'); + if (ctx) { + const Cx = this.props.cachedCenteringShiftX % renderGridSpace; + const Cy = this.props.cachedCenteringShiftY % renderGridSpace; + ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling())); + ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); + ctx.clearRect(0, 0, w, h); + if (ctx) { + ctx.strokeStyle = strokeStyle; + ctx.beginPath(); + for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { + ctx.moveTo(x, Cy - h); + ctx.lineTo(x, Cy + h); + } + for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { + ctx.moveTo(Cx - w, y); + ctx.lineTo(Cx + w, y); + } + ctx.stroke(); + } + } + }} />; + } }
\ 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 08da682bb..b10b0912f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -30,6 +30,9 @@ import "./MarqueeView.scss"; import React = require("react"); import { StyleLayers } from "../../StyleProvider"; import { TreeView } from "../TreeView"; +import { VideoBox } from "../../nodes/VideoBox"; +import { ImageField, WebField } from "../../../../fields/URLField"; +import { pasteImageBitmap } from "../../nodes/WebBoxRenderer"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -134,17 +137,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque })(); e.stopPropagation(); } else if (e.key === "b" && e.ctrlKey) { - // e.preventDefault(); - // navigator.clipboard.readText().then(text => { - // const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== ""); - // if (ns.length === 1 && text.startsWith("http")) { - // this.props.addDocument(Docs.Create.ImageDocument(text, { _nativeWidth: 300, _width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer - // } else { - // this.pasteTable(ns, x, y); - // } - // }); - // e.stopPropagation(); - + document.body.focus(); // so that we can access the clipboard without an error + setTimeout(() => + pasteImageBitmap((data: any, error: any) => { + error && console.log(error); + data && VideoBox.convertDataUri(data, this.props.Document[Id] + "-thumb-frozen").then(returnedfilename => { + this.props.Document["thumb-frozen"] = new ImageField(returnedfilename); + }); + })); + } else if (e.key === "s" && e.ctrlKey) { e.preventDefault(); const slide = Doc.copyDragFactory(Doc.UserDoc().emptySlide as Doc)!; slide.x = x; diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index b0030471d..58ea7410d 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -2,9 +2,7 @@ import { action, computed, Lambda, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; import { Doc, Opt } from '../../../../fields/Doc'; -import { documentSchema } from '../../../../fields/documentSchemas'; import { Id } from '../../../../fields/FieldSymbols'; -import { makeInterface } from '../../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; @@ -20,11 +18,8 @@ import { CollectionSubView } from '../CollectionSubView'; import "./CollectionGridView.scss"; import Grid, { Layout } from "./Grid"; -type GridSchema = makeInterface<[typeof documentSchema]>; -const GridSchema = makeInterface(documentSchema); - @observer -export class CollectionGridView extends CollectionSubView(GridSchema) { +export class CollectionGridView extends CollectionSubView() { private _containerRef: React.RefObject<HTMLDivElement> = React.createRef(); private _changeListenerDisposer: Opt<Lambda>; // listens for changes in this.childLayoutPairs private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 70c8c9436..160134b60 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -4,9 +4,7 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mo import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; -import { documentSchema } from '../../../../fields/documentSchemas'; import { Id } from '../../../../fields/FieldSymbols'; -import { makeInterface } from '../../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; import { DocUtils } from '../../../documents/Documents'; @@ -25,9 +23,6 @@ import { CollectionViewType } from '../CollectionView'; import "./CollectionLinearView.scss"; -type LinearDocument = makeInterface<[typeof documentSchema,]>; -const LinearDocument = makeInterface(documentSchema); - /** * CollectionLinearView is the class for rendering the horizontal collection * of documents, it useful for horizontal menus. It can either be expandable @@ -37,7 +32,7 @@ const LinearDocument = makeInterface(documentSchema); * - It is used for the context sensitive toolbar at the top (see contMenuButtons() in CollectionMenu.tsx) */ @observer -export class CollectionLinearView extends CollectionSubView(LinearDocument) { +export class CollectionLinearView extends CollectionSubView() { @observable public addMenuToggle = React.createRef<HTMLInputElement>(); @observable private _selectedIndex = -1; private _dropDisposer?: DragManager.DragDropDisposer; @@ -78,7 +73,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { { fireImmediately: true } ); } - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index ec1cbadd5..2bdf92417 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -2,11 +2,9 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; import { Doc } from '../../../../fields/Doc'; -import { documentSchema } from '../../../../fields/documentSchemas'; import { List } from '../../../../fields/List'; -import { makeInterface } from '../../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { returnFalse, emptyPath, returnEmptyDoclist, emptyFunction } from '../../../../Utils'; +import { emptyFunction, returnFalse } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -16,8 +14,6 @@ import "./CollectionMulticolumnView.scss"; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; -type MulticolumnDocument = makeInterface<[typeof documentSchema]>; -const MulticolumnDocument = makeInterface(documentSchema); interface WidthSpecifier { magnitude: number; @@ -38,7 +34,7 @@ const resolvedUnits = Object.values(DimUnit); const resizerWidth = 8; @observer -export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocument) { +export class CollectionMulticolumnView extends CollectionSubView() { /** * @returns the list of layout documents whose width unit is @@ -51,7 +47,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu @computed private get minimumDim() { - return Math.min(...this.ratioDefinedDocs.filter(layout => layout._dimMagnitude).map(layout => NumCast(layout._dimMagnitude))); + const ratioDocs = this.ratioDefinedDocs.filter(layout => layout._dimMagnitude); + return ratioDocs.length ? Math.min(...ratioDocs.map(layout => NumCast(layout._dimMagnitude))) : 1; } /** diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index a2d51e2e7..7e2b83230 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -2,11 +2,9 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; import { Doc } from '../../../../fields/Doc'; -import { documentSchema } from '../../../../fields/documentSchemas'; import { List } from '../../../../fields/List'; -import { makeInterface } from '../../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { returnFalse, emptyPath, returnEmptyDoclist, emptyFunction } from '../../../../Utils'; +import { emptyFunction, returnFalse } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -16,9 +14,6 @@ import "./CollectionMultirowView.scss"; import HeightLabel from './MultirowHeightLabel'; import ResizeBar from './MultirowResizer'; -type MultirowDocument = makeInterface<[typeof documentSchema]>; -const MultirowDocument = makeInterface(documentSchema); - interface HeightSpecifier { magnitude: number; unit: string; @@ -38,7 +33,7 @@ const resolvedUnits = Object.values(DimUnit); const resizerHeight = 8; @observer -export class CollectionMultirowView extends CollectionSubView(MultirowDocument) { +export class CollectionMultirowView extends CollectionSubView() { /** * @returns the list of layout documents whose width unit is @@ -51,7 +46,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) @computed private get minimumDim() { - return Math.min(...this.ratioDefinedDocs.filter(layout => layout._dimMagnitude).map(layout => NumCast(layout._dimMagnitude))); + const ratioDocs = this.ratioDefinedDocs.filter(layout => layout._dimMagnitude); + return ratioDocs.length ? Math.min(...ratioDocs.map(layout => NumCast(layout._dimMagnitude))) : 1; } /** diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index a439a7998..c2bb3b3ac 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -2,19 +2,18 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; +import { extname } from "path"; import DatePicker from "react-datepicker"; -import "react-datepicker/dist/react-datepicker.css"; import { CellInfo } from "react-table"; -import "react-table/react-table.css"; import { DateField } from "../../../../fields/DateField"; import { Doc, DocListCast, Field, Opt } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { List } from "../../../../fields/List"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { ComputedField } from "../../../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; +import { BoolCast, Cast, DateCast, FieldValue, StrCast } from "../../../../fields/Types"; import { ImageField } from "../../../../fields/URLField"; -import { Utils, emptyFunction } from "../../../../Utils"; +import { emptyFunction, Utils } from "../../../../Utils"; import { Docs } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -29,9 +28,8 @@ import { EditableView } from "../../EditableView"; import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss'; import { DocumentIconContainer } from "../../nodes/DocumentIcon"; import { OverlayView } from "../../OverlayView"; -import "./CollectionSchemaView.scss"; import { CollectionView } from "../CollectionView"; -const path = require('path'); +import "./CollectionSchemaView.scss"; // intialize cell properties export interface CellProps { @@ -495,8 +493,8 @@ export class CollectionSchemaImageCell extends CollectionSchemaCell { if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href;//Why is this here — good question - const ext = path.extname(url.href); // the extension of the file - return url.href.replace(ext, "_o" + path.extname(url.href)); + const ext = extname(url.href); + return url.href.replace(ext, "_o" + ext); } render() { diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index b89246489..8b73351d5 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -4,7 +4,6 @@ import { action, computed, observable, untracked, trace } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import { Resize } from "react-table"; -import "react-table/react-table.css"; import { Doc, Opt } from "../../../../fields/Doc"; import { List } from "../../../../fields/List"; import { listSpec } from "../../../../fields/Schema"; @@ -47,7 +46,7 @@ const columnTypes: Map<string, ColumnType> = new Map([ ]); @observer -export class CollectionSchemaView extends CollectionSubView(doc => doc) { +export class CollectionSchemaView extends CollectionSubView() { private _previewCont?: HTMLDivElement; @observable _previewDoc: Doc | undefined = undefined; @@ -147,43 +146,43 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Any)}> <FontAwesomeIcon icon={"align-justify"} size="sm" /> - Any - </div>; + Any + </div>; const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Number)}> <FontAwesomeIcon icon={"hashtag"} size="sm" /> - Number - </div>; + Number + </div>; const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.String)}> <FontAwesomeIcon icon={"font"} size="sm" /> Text - </div>; + </div>; const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Boolean)}> <FontAwesomeIcon icon={"check-square"} size="sm" /> Checkbox - </div>; + </div>; const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.List)}> <FontAwesomeIcon icon={"list-ul"} size="sm" /> List - </div>; + </div>; const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Doc)}> <FontAwesomeIcon icon={"file"} size="sm" /> Document - </div>; + </div>; const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Image)}> <FontAwesomeIcon icon={"image"} size="sm" /> Image - </div>; + </div>; const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Date)}> <FontAwesomeIcon icon={"calendar"} size="sm" /> - Date - </div>; + Date + </div>; const allColumnTypes = <div className="columnMenu-types"> @@ -557,7 +556,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { style={{ overflow: this.props.scrollOverflow === true ? "scroll" : undefined, backgroundColor: "white", pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined, - width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", + width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > <div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.previewWidth()}px)` }} diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index 2219345f6..605481ddf 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -4,7 +4,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table"; -import "react-table/react-table.css"; import { DateField } from "../../../../fields/DateField"; import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index eb7b9a773..c2b4d0eee 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -215,7 +215,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp this.dataDoc[this.fieldKey + "-recordingStart"] = new DateField(); DocUtils.ActiveRecordings.push(this); this._recorder.ondataavailable = async (e: any) => { + console.log("Data available", e); const [{ result }] = await Networking.UploadFilesToServer(e.data); + console.log("Data result", result); if (!(result instanceof Error)) { this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client); } @@ -602,4 +604,4 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp {!this.path ? this.recordingControls : this.playbackControls} </div>; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index fe34d6687..c2a526804 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,7 +1,6 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, Opt } from "../../../fields/Doc"; -import { Document } from "../../../fields/documentSchemas"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; @@ -23,25 +22,27 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined; layerProvider: ((doc: Doc, assign?: boolean) => boolean) | undefined; + renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; highlight?: boolean; jitterRotation: number; dataTransition?: string; replica: string; + renderIndex: number; CollectionFreeFormView: CollectionFreeFormView; } @observer -export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, Document>(Document) { +export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() { public static animFields = ["_height", "_width", "x", "y", "_scrollTop", "opacity"]; // fields that are configured to be animatable using animation frames @observable _animPos: number[] | undefined = undefined; @observable _contentView: DocumentView | undefined | null; get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive get maskCentering() { return this.props.Document.isInkMask ? InkingStroke.MaskDim / 2 : 0; } get transform() { return `translate(${this.X - this.maskCentering}px, ${this.Y - this.maskCentering}px) rotate(${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); } + get X() { return this.dataProvider ? this.dataProvider.x : NumCast(this.Document.x); } + get Y() { return this.dataProvider ? this.dataProvider.y : NumCast(this.Document.y); } + get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); } get Opacity() { return this.dataProvider ? this.dataProvider.opacity : undefined; } get Highlight() { return this.dataProvider?.highlight; } @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); } @@ -176,7 +177,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF mixBlendMode, display: this.ZInd === -99 ? "none" : undefined }} > - <DocumentView {...divProps} ref={action((r: DocumentView | null) => this._contentView = r)} /> + {this.props.renderCutoffProvider(this.props.Document) ? + <div style={{ position: "absolute", width: this.panelWidth(), height: this.panelHeight(), background: "lightGreen" }} /> + : + <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 8da5cd1b1..d975baf9b 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -3,9 +3,7 @@ import { action } from "mobx"; import { observer } from "mobx-react"; import { ColorState, SketchPicker } from 'react-color'; import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; -import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; -import { makeInterface } from "../../../fields/Schema"; import { StrCast } from "../../../fields/Types"; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -17,11 +15,8 @@ import "./ColorBox.scss"; import { FieldView, FieldViewProps } from './FieldView'; import { RichTextMenu } from "./formattedText/RichTextMenu"; -type ColorDocument = makeInterface<[typeof documentSchema]>; -const ColorDocument = makeInterface(documentSchema); - @observer -export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument>(ColorDocument) { +export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); } @undoBatch diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index d47e8340c..cbc61ffdb 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -2,8 +2,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import { Doc, Opt } from '../../../fields/Doc'; -import { documentSchema } from '../../../fields/documentSchemas'; -import { createSchema, makeInterface } from '../../../fields/Schema'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; @@ -15,15 +13,10 @@ import "./ComparisonBox.scss"; import { DocumentView, DocumentViewProps } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import React = require("react"); -import { DocumentType } from '../../documents/DocumentTypes'; -export const comparisonSchema = createSchema({}); - -type ComparisonDocument = makeInterface<[typeof comparisonSchema, typeof documentSchema]>; -const ComparisonDocument = makeInterface(comparisonSchema, documentSchema); @observer -export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ComparisonDocument>(ComparisonDocument) { +export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); } protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined; private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined]; diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx index 123212608..433a0bf48 100644 --- a/src/client/views/nodes/DocumentIcon.tsx +++ b/src/client/views/nodes/DocumentIcon.tsx @@ -2,7 +2,7 @@ import { observer } from "mobx-react"; import * as React from "react"; import { DocumentView } from "./DocumentView"; import { DocumentManager } from "../../util/DocumentManager"; -import { Transformer, Scripting, ts } from "../../util/Scripting"; +import { Transformer, ts } from "../../util/Scripting"; import { Field } from "../../../fields/Doc"; @observer diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss index 228e1bdcb..9ab3171d3 100644 --- a/src/client/views/nodes/DocumentLinksButton.scss +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -1,6 +1,8 @@ @import "../global/globalCssVariables.scss"; - +.documentLinksButton-wrapper { + transform-origin: top left; +} .documentLinksButton-menu { width: 100%; height: 100%; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 93cd02d93..7e6ca4248 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -20,6 +20,7 @@ import { DocumentView } from "./DocumentView"; import { LinkDescriptionPopup } from "./LinkDescriptionPopup"; import { TaskCompletionBox } from "./TaskCompletedBox"; import React = require("react"); +import { Transform } from "../../util/Transform"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -31,6 +32,7 @@ interface DocumentLinksButtonProps { AlwaysOn?: boolean; InMenu?: boolean; StartLink?: boolean; //whether the link HAS been started (i.e. now needs to be completed) + ContentScaling?: () => number; } @observer export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> { @@ -302,16 +304,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp const title = this.props.InMenu ? menuTitle : buttonTitle; //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu - return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? (null) : - this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ? - <Tooltip title={<div className="dash-tooltip">{title}</div>}> - {this.linkButtonInner} - </Tooltip> - : - !DocumentLinksButton.LinkEditorDocView && !this.props.InMenu ? - <Tooltip title={<div className="dash-tooltip">{title}</div>}> - {this.linkButtonInner} - </Tooltip> - : this.linkButtonInner; + return (!Array.from(this.filteredLinks).length && !this.props.AlwaysOn) ? (null) : + <div className="documentLinksButton-wrapper" > + { + (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || + (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? + <Tooltip title={<div className="dash-tooltip">{title}</div>}> + {this.linkButtonInner} + </Tooltip> + : this.linkButtonInner + } + </div>; } } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 9fcd45e72..4565f8504 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -58,9 +58,10 @@ .documentView-audioBackground { display: inline-block; width: 10%; + height: 25; position: absolute; - top: 0px; - left: 0px; + top: 10px; + left: 10px; border-radius: 25px; background: white; opacity: 0.3; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 707c15bff..e7de87ea5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,3 +1,4 @@ +import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; @@ -9,11 +10,11 @@ import { List } from "../../../fields/List"; import { ObjectField } from "../../../fields/ObjectField"; import { listSpec } from "../../../fields/Schema"; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { AudioField } from "../../../fields/URLField"; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; -import { emptyFunction, hasDescendantTarget, OmitKeys, returnTrue, returnVal, Utils, lightOrDark, simulateMouseClick, returnEmptyString } from "../../../Utils"; +import { emptyFunction, hasDescendantTarget, lightOrDark, OmitKeys, returnEmptyString, returnTrue, returnVal, simulateMouseClick, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; @@ -23,7 +24,7 @@ import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; import { InteractionUtils } from '../../util/InteractionUtils'; import { LinkManager } from '../../util/LinkManager'; -import { Scripting } from '../../util/Scripting'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from "../../util/SelectionManager"; import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -48,8 +49,6 @@ import { RadialMenu } from './RadialMenu'; import { ScriptingBox } from "./ScriptingBox"; import { PresBox } from './trails/PresBox'; import React = require("react"); -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { ColorScheme } from "../../util/SettingsManager"; const { Howl } = require('howler'); interface Window { @@ -110,6 +109,7 @@ export interface DocumentViewSharedProps { fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document ContainingCollectionView: Opt<CollectionView>; ContainingCollectionDoc: Opt<Doc>; + thumbShown?: () => boolean; setContentView?: (view: DocComponentView) => any; CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView; PanelWidth: () => number; @@ -179,11 +179,12 @@ export interface DocumentViewInternalProps extends DocumentViewProps { } @observer -export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps, Document>(Document) { +export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() { public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered. @observable _animateScalingTo = 0; @observable _mediaState = 0; @observable _pendingDoubleClick = false; + private _disposers: { [name: string]: IReactionDisposer } = {}; private _downX: number = 0; private _downY: number = 0; private _firstX: number = -1; @@ -204,6 +205,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); } @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); } @computed get ContentScale() { return this.props.ContentScaling?.() || 1; } + @computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); } @computed get hidden() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Hidden); } @computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); } @computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); } @@ -239,6 +241,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this._multiTouchDisposer?.(); this._holdDisposer?.(); unbrush && Doc.UnBrushDoc(this.props.Document); + Object.values(this._disposers).forEach(disposer => disposer?.()); } handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => { @@ -485,6 +488,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const func = () => this.onClickHandler.script.run({ this: this.layoutDoc, self: this.rootDoc, + _readOnly_: false, scriptContext: this.props.scriptContext, thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.DocumentView(), @@ -836,14 +840,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive()) ? true : undefined; } + @observable _retryThumb = 1; + thumbShown = () => !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb && + !this._componentView?.isAnyChildContentActive?.() ? true : false; @computed get contents() { TraceMobx(); const audioView = !this.layoutDoc._showAudio ? (null) : - <div className="documentView-audioBackground" - onPointerDown={this.recordAudioAnnotation} - onPointerEnter={this.onPointerEnter} - style={{ height: 25, position: "absolute", top: 10, left: 10 }} - > + <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter} > <FontAwesomeIcon className="documentView-audioFont" style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }} icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" /> @@ -853,8 +856,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps pointerEvents: this.props.pointerEvents as any ? this.props.pointerEvents as any : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"), height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined, }}> - <DocumentContentsView key={1} {...this.props} + {!this._retryThumb || !this.thumbShown() ? (null) : + <img style={{ background: "white", top: 0, position: "absolute" }} src={this.thumb} // + '?d=' + (new Date()).getTime()} + width={this.props.PanelWidth()} height={this.props.PanelHeight()} + onError={(e: any) => { + setTimeout(action(() => this._retryThumb = 0), 0); + setTimeout(action(() => this._retryThumb = 1), 150); + }} />} + <DocumentContentsView key={1} + {...this.props} docViewPath={this.props.viewPath} + thumbShown={this.thumbShown} setContentView={this.setContentView} scaling={this.contentScaling} PanelHeight={this.panelHeight} @@ -866,11 +878,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps focus={this.focus} layoutKey={this.finalLayoutKey} /> {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} - {this.hideLinkButton || this.props.renderDepth === -1 ? (null) : - <div style={{ transformOrigin: "top left", transform: `scale(${Math.min(1, this.props.ScreenToLocalTransform().scale(this.props.ContentScaling?.() || 1).Scale)})` }}> - <DocumentLinksButton View={this.props.DocumentView()} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} /> - </div>} - + {this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) : + <DocumentLinksButton View={this.props.DocumentView()} + ContentScaling={this.props.ContentScaling} + Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} /> + } {audioView} </div>; } @@ -984,7 +996,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption"); @computed get innards() { TraceMobx(); - const ffscale = (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1); + const ffscale = () => (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1); const showTitle = this.ShowTitle?.split(":")[0]; const showTitleHover = this.ShowTitle?.includes(":hover"); const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined; @@ -992,14 +1004,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps <div className="documentView-captionWrapper" style={{ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined, - minWidth: 50 * ffscale, - maxHeight: `max(100%, ${20 * ffscale}px)` + minWidth: 50 * ffscale(), + maxHeight: `max(100%, ${20 * ffscale()}px)` }}> <FormattedTextBox {...OmitKeys(this.props, ['children']).omit} yPadding={10} xPadding={10} fieldKey={showCaption} - fontSize={12 * Math.max(1, 2 * ffscale / 3)} + fontSize={12 * Math.max(1, 2 * ffscale() / 3)} styleProvider={this.captionStyleProvider} dontRegisterView={true} noSidebar={true} @@ -1023,19 +1035,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps display={"block"} fontSize={10} GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle} - SetValue={undoBatch(input => { + 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"; } - return true; } else { - var value = input.replace(new RegExp(showTitle + "="), ""); + 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; - return Doc.SetInPlace(targetDoc, showTitle, value, true) ? true : true; + Doc.SetInPlace(targetDoc, showTitle, value, true); } return true; })} @@ -1048,15 +1059,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps {captionView} </div>; } + @observable _: string = ""; @computed get renderDoc() { TraceMobx(); - const isButton: boolean = this.props.Document.type === DocumentType.FONTICON; + const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); + const isButton = this.props.Document.type === DocumentType.FONTICON; if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null; return this.docContents ?? <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} id={this.props.Document[Id]} style={{ - background: isButton ? undefined : this.backgroundColor, + background: isButton || thumb ? undefined : this.backgroundColor, opacity: this.opacity, color: StrCast(this.layoutDoc.color, "inherit"), fontFamily: StrCast(this.Document._fontFamily, "inherit"), @@ -1065,6 +1078,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined, transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform 0.5s ease-${this._animateScalingTo < 1 ? "in" : "out"}`, }}> + {this.innards} {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : (null)} {this.widgetDecorations ?? null} @@ -1143,7 +1157,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; } get fitWidth() { return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; } - @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; } + @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; } @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); } @computed get nativeWidth() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : @@ -1281,7 +1295,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { } } -Scripting.addGlobal(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { +ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout"); else dv.switchViews(true, detailLayoutKeySuffix); });
\ No newline at end of file diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index f1f802c13..c170f9867 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -3,25 +3,18 @@ import { action, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { WidthSym } from '../../../fields/Doc'; -import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; -import { createSchema, makeInterface } from '../../../fields/Schema'; import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { Docs } from '../../documents/Documents'; import { ViewBoxBaseComponent } from '../DocComponent'; import { LightboxView } from '../LightboxView'; -import { FieldView, FieldViewProps } from './FieldView'; import './EquationBox.scss'; +import { FieldView, FieldViewProps } from './FieldView'; -const EquationSchema = createSchema({}); - -type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSchema]>; -const EquationDocument = makeInterface(EquationSchema, documentSchema); - @observer -export class EquationBox extends ViewBoxBaseComponent<FieldViewProps, EquationDocument>(EquationDocument) { +export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); } public static SelectOnLoad: string = ""; _ref: React.RefObject<EquationEditor> = React.createRef(); diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index fb8e89da9..ba65acee0 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -4,18 +4,17 @@ import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; 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"; -import { listSpec, makeInterface } from "../../../fields/Schema"; +import { listSpec } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; -import { Cast, StrCast, NumCast } from "../../../fields/Types"; +import { Cast, NumCast, 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 { ScriptingGlobals } from "../../util/ScriptingGlobals"; import { SelectionManager } from "../../util/SelectionManager"; import { CollectionTreeView } from "../collections/CollectionTreeView"; import { CollectionView } from "../collections/CollectionView"; @@ -30,11 +29,8 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -type FilterBoxDocument = makeInterface<[typeof documentSchema]>; -const FilterBoxDocument = makeInterface(documentSchema); - @observer -export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDocument>(FilterBoxDocument) { +export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() { constructor(props: Readonly<FieldViewProps>) { super(props); @@ -425,7 +421,6 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc treeViewHideTitle={true} focus={returnFalse} treeViewHideHeaderFields={false} - onCheckedClick={this.scriptField} dontRegisterView={true} styleProvider={this.FilterStyleProvider} layerProvider={this.props.layerProvider} @@ -475,7 +470,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc } } -Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { +ScriptingGlobals.add(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []); for (const filter of docFilters) { const fields = filter.split(":"); // split into key:value:modifiers @@ -485,7 +480,7 @@ Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: } return undefined; }); -Scripting.addGlobal(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) { +ScriptingGlobals.add(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) { const allCollectionDocs = new Set<Doc>(); const activeTabs = DocListCast(layoutDoc[childKey]); SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 5050fc2d2..3ab0a3ff2 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -19,7 +19,7 @@ type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSch const EquationDocument = makeInterface(EquationSchema, documentSchema); @observer -export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps, EquationDocument>(EquationDocument) { +export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FunctionPlotBox, fieldKey); } public static GraphCount = 0; _plot: any; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 89f70985c..0a4168698 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,12 +1,12 @@ -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; +import { extname } from 'path'; import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc'; -import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; -import { createSchema, makeInterface } from '../../../fields/Schema'; +import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; @@ -29,16 +29,11 @@ import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { SnappingManager } from '../../util/SnappingManager'; -const path = require('path'); export const pageSchema = createSchema({ googlePhotosUrl: "string", googlePhotosTags: "string" }); -type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; -const ImageDocument = makeInterface(pageSchema, documentSchema); - const uploadIcons = { idle: "downarrow.png", loading: "loading.gif", @@ -47,7 +42,7 @@ const uploadIcons = { }; @observer -export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ImageDocument>(ImageDocument) { +export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } private _imgRef: React.RefObject<HTMLImageElement> = React.createRef(); @@ -190,7 +185,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return url.href; //Why is this here - const ext = path.extname(url.href); + const ext = extname(url.href); return url.href.replace(ext, this._curSuffix + ext); } @@ -286,7 +281,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}> - <div className="imageBox-fader" style={{ overflow: this.props.docViewPath?.().lastElement().fitWidth ? "auto" : undefined }} > + <div className="imageBox-fader" style={{ overflow: Array.from(this.props.docViewPath?.()).slice(-1)[0].fitWidth ? "auto" : undefined }} > <img key="paths" ref={this._imgRef} src={srcpath} style={{ transform, transformOrigin }} @@ -376,7 +371,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp scrollTop={0} down={this._marqueeing} scaling={this.props.scaling} - docView={this.props.docViewPath().lastElement()} + docView={this.props.docViewPath().slice(-1)[0]} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 111509fdb..c44c8c828 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -58,7 +58,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { value = eq ? value.substr(1) : value; const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false; value = dubEq ? value.substr(2) : value; - const options: ScriptOptions = { addReturn: true, params: { this: "Doc", _last_: "any" }, editable: false }; + const options: ScriptOptions = { addReturn: true, params: { this: Doc.name, self: Doc.name, _last_: "any", _readOnly_: "boolean" }, editable: false }; if (dubEq) options.typecheck = false; const script = CompileScript(value, options); return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq }; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index dae72bb78..0015f0b71 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -2,9 +2,8 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; -import { documentSchema } from '../../../fields/documentSchemas'; import { List } from '../../../fields/List'; -import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { listSpec } from '../../../fields/Schema'; import { Cast, StrCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; @@ -16,17 +15,13 @@ import { FieldView, FieldViewProps } from './FieldView'; import BigText from './LabelBigText'; import './LabelBox.scss'; -const LabelSchema = createSchema({}); - -type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>; -const LabelDocument = makeInterface(LabelSchema, documentSchema); export interface LabelBoxProps { label?: string; } @observer -export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps), LabelDocument>(LabelDocument) { +export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps)>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); } public static LayoutStringWithTitle(fieldType: { name: string }, fieldStr: string, label: string) { return `<${fieldType.name} fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />" diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index a7bfb93eb..437d29f39 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -2,8 +2,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../fields/Doc"; -import { documentSchema } from "../../../fields/documentSchemas"; -import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -23,11 +21,9 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -type LinkAnchorSchema = makeInterface<[typeof documentSchema]>; -const LinkAnchorDocument = makeInterface(documentSchema); @observer -export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnchorSchema>(LinkAnchorDocument) { +export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkAnchorBox, fieldKey); } _doubleTap = false; _lastTap: number = 0; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 879a63248..43f4b43fb 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,7 +1,5 @@ import React = require("react"); import { observer } from "mobx-react"; -import { documentSchema } from "../../../fields/documentSchemas"; -import { makeInterface } from "../../../fields/Schema"; import { emptyFunction, returnFalse } from "../../../Utils"; import { ViewBoxBaseComponent } from "../DocComponent"; import { StyleProp } from "../StyleProvider"; @@ -9,11 +7,8 @@ import { ComparisonBox } from "./ComparisonBox"; import { FieldView, FieldViewProps } from './FieldView'; import "./LinkBox.scss"; -type LinkDocument = makeInterface<[typeof documentSchema]>; -const LinkDocument = makeInterface(documentSchema); - @observer -export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(LinkDocument) { +export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); } isContentActiveFunc = () => this.isContentActive(); render() { diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 50444c73a..aa2130af5 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -1,14 +1,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api'; -import * as dotenv from 'dotenv'; import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx'; import { observer } from "mobx-react"; import * as React from "react"; import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc'; -import { documentSchema } from '../../../../fields/documentSchemas'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; -import { makeInterface } from '../../../../fields/Schema'; import { NumCast, StrCast } from '../../../../fields/Types'; import { TraceMobx } from '../../../../fields/util'; import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; @@ -43,9 +40,6 @@ import { MapBoxInfoWindow } from './MapBoxInfoWindow'; // const _global = (window /* browser */ || global /* node */) as any; -type MapDocument = makeInterface<[typeof documentSchema]>; -const MapDocument = makeInterface(documentSchema); - const mapContainerStyle = { height: '100%', }; @@ -59,7 +53,6 @@ const mapOptions = { fullscreenControl: false, }; -dotenv.config({ path: __dirname + '/.env' }); const apiKey = process.env.GOOGLE_MAPS; const script = document.createElement('script'); @@ -93,7 +86,7 @@ const options = { } as google.maps.places.AutocompleteOptions; @observer -export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>, MapDocument>(MapDocument) { +export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() { private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index f44355929..8a68f9647 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -248,6 +248,7 @@ .pdfBox-background { width: 100%; height: 100%; + cursor: ew-resize; background: lightGray; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index d54b65d92..9807cee7c 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -3,17 +3,14 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, Opt, WidthSym, StrListCast } from "../../../fields/Doc"; -import { documentSchema } from '../../../fields/documentSchemas'; -import { makeInterface, listSpec } from "../../../fields/Schema"; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc"; +import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types'; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { KeyCodes } from '../../util/KeyCodes'; import { undoBatch } from '../../util/UndoManager'; -import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; @@ -22,18 +19,14 @@ import { AnchorMenu } from '../pdf/AnchorMenu'; import { PDFViewer } from "../pdf/PDFViewer"; import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; -import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; - -type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; -const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @observer -export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, PdfDocument>(PdfDocument) { +export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } public static openSidebarWidth = 250; + public static sidebarResizerWidth = 5; private _searchString: string = ""; private _initialScrollTarget: Opt<Doc>; private _pdfViewer: PDFViewer | undefined; @@ -46,6 +39,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable private _pageControls = false; @computed get pdfUrl() { return Cast(this.dataDoc[this.props.fieldKey], PdfField); } + @computed get pdfThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; } constructor(props: any) { super(props); @@ -54,7 +48,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); if (this.pdfUrl) { if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)); - else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action(pdf => this._pdf = pdf)); + else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => this._pdf = pdf)); } } @@ -80,7 +74,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const anchor = AnchorMenu.Instance?.GetAnchor() ?? Docs.Create.TextanchorDocument({ - title: StrCast(this.rootDoc.title + "@" + this.layoutDoc._scrollTop?.toFixed(0)), + title: StrCast(this.rootDoc.title + "@" + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), y: NumCast(this.layoutDoc._scrollTop), unrendered: true }); @@ -110,8 +104,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); public prevAnnotation = () => this._pdfViewer?.prevAnnotation(); public nextAnnotation = () => this._pdfViewer?.nextAnnotation(); - public backPage = () => { this.Document._curPage = (this.Document._curPage || 1) - 1; return true; }; - public forwardPage = () => { this.Document._curPage = (this.Document._curPage || 1) + 1; return true; }; + public backPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) - 1; return true; }; + public forwardPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) + 1; return true; }; public gotoPage = (p: number) => this.Document._curPage = p; @undoBatch @@ -142,15 +136,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (!this.layoutDoc._showSidebar) this.toggleSidebar(); return this.addDocument(doc, sidebarKey); } - sidebarBtnDown = (e: React.PointerEvent) => { + sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf setupMoveUpEvents(this, e, (e, down, delta) => { const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; + const ratio = (curNativeWidth + (onButton ? 1 : -1) * localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; if (ratio >= 1) { this.layoutDoc.nativeWidth = nativeWidth * ratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; + onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]); this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } return false; @@ -160,16 +154,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable _previewWidth: Opt<number> = undefined; toggleSidebar = action((preview: boolean = false) => { const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); - const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; + const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; + const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth; const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); if (preview) { - this._previewNativeWidth = nativeWidth * ratio; - this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth; + this._previewNativeWidth = nativeWidth * sideratio; + this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * sideratio / curNativeWidth; this._showSidebar = true; } else { - this.layoutDoc.nativeWidth = nativeWidth * ratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth; + this.layoutDoc.nativeWidth = nativeWidth * pdfratio; + this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * pdfratio / curNativeWidth; this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } }); @@ -185,7 +180,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </button> </>; const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`; - const curPage = this.Document._curPage || 1; + const curPage = NumCast(this.Document._curPage) || 1; return !this.props.isContentActive() ? (null) : <div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true} onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}> @@ -223,7 +218,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5, backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK }} - onPointerDown={this.sidebarBtnDown} > + onPointerDown={e => this.sidebarBtnDown(e, true)} > <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" /> </div> </div>; @@ -253,18 +248,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } contentScaling = () => 1; + isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected(); @computed get renderPdfView() { TraceMobx(); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const scale = previewScale * (this.props.scaling?.() || 1); return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ + display: this.props.thumbShown?.() ? "none" : undefined, height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}> - <div className="pdfBox-background" /> + <div className="pdfBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} /> <div style={{ - width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`, + width: `calc(${100 / scale}% - ${(this.sidebarWidth() + PDFBox.sidebarResizerWidth) / scale * (this._previewWidth ? scale : 1)}px)`, height: `${100 / scale}%`, transform: `scale(${scale})`, position: "absolute", @@ -277,7 +274,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps dataDoc={this.dataDoc} pdf={this._pdf!} url={this.pdfUrl!.url.pathname} - isContentActive={this.props.isContentActive} + isContentActive={this.isPdfContentActive} anchorMenuClick={this.anchorMenuClick} loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined} setPdfViewer={this.setPdfViewer} @@ -307,17 +304,22 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } static pdfcache = new Map<string, Pdfjs.PDFDocumentProxy>(); - static pdfpromise = new Map<string, Pdfjs.PDFPromise<Pdfjs.PDFDocumentProxy>>(); + static pdfpromise = new Map<string, Promise<Pdfjs.PDFDocumentProxy>>(); render() { TraceMobx(); - if (this._pdf) return this.renderPdfView; + if (this._pdf) { + if (!this.props.thumbShown?.()) { + return this.renderPdfView; + } + return null; + } const href = this.pdfUrl?.url.href; if (href) { if (PDFBox.pdfcache.get(href)) setTimeout(action(() => this._pdf = PDFBox.pdfcache.get(href))); else { if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise); - PDFBox.pdfpromise.get(href)?.then(action(pdf => PDFBox.pdfcache.set(href, this._pdf = pdf))); + PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, this._pdf = pdf))); } } return this.renderTitleBox; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 0c631e5f9..dbb567d3a 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -1,20 +1,17 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; // import { Canvas } from '@react-three/fiber'; -import { action, computed, observable, reaction, trace, runInAction } from "mobx"; +import { computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three'; import { DateField } from "../../../fields/DateField"; -import { Doc, WidthSym, HeightSym } from "../../../fields/Doc"; -import { documentSchema } from "../../../fields/documentSchemas"; +import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; -import { InkTool } from "../../../fields/InkField"; -import { makeInterface } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; import { AudioField, VideoField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, numberRange, OmitKeys, returnFalse, returnOne, Utils } from "../../../Utils"; +import { emptyFunction, OmitKeys, returnFalse, returnOne } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { Networking } from "../../Network"; @@ -32,9 +29,6 @@ declare class MediaRecorder { constructor(e: any, options?: any); // whatever MediaRecorder has } -type ScreenshotDocument = makeInterface<[typeof documentSchema]>; -const ScreenshotDocument = makeInterface(documentSchema); - // interface VideoTileProps { // raised: { coord: Vector2, off: Vector3 }[]; // setRaised: (r: { coord: Vector2, off: Vector3 }[]) => void; @@ -112,7 +106,7 @@ const ScreenshotDocument = makeInterface(documentSchema); // } @observer -export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ScreenshotDocument>(ScreenshotDocument) { +export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); } private _audioRec: any; private _videoRec: any; diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 183140cd7..366c3fc2f 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -1,18 +1,19 @@ import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete"; import "@webscopeio/react-textarea-autocomplete/style.css"; -import { action, computed, observable, runInAction, trace } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; import { Doc } from "../../../fields/Doc"; -import { documentSchema } from "../../../fields/documentSchemas"; import { List } from "../../../fields/List"; -import { createSchema, listSpec, makeInterface } from "../../../fields/Schema"; +import { listSpec } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast, ScriptCast, StrCast, BoolCast } from "../../../fields/Types"; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; import { returnEmptyString } from "../../../Utils"; import { DragManager } from "../../util/DragManager"; import { InteractionUtils } from "../../util/InteractionUtils"; -import { CompileScript, Scripting, ScriptParam } from "../../util/Scripting"; +import { CompileScript, ScriptParam } from "../../util/Scripting"; +import { ScriptingGlobals } from "../../util/ScriptingGlobals"; import { ScriptManager } from "../../util/ScriptManager"; import { ContextMenu } from "../ContextMenu"; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; @@ -21,15 +22,10 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView"; import { OverlayView } from "../OverlayView"; import { DocumentIconContainer } from "./DocumentIcon"; import "./ScriptingBox.scss"; -import { TraceMobx } from "../../../fields/util"; const _global = (window /* browser */ || global /* node */) as any; -const ScriptingSchema = createSchema({}); -type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentSchema]>; -const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema); - @observer -export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ScriptingDocument>(ScriptingDocument) { +export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { private dropDisposer?: DragManager.DragDropDisposer; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined; @@ -42,9 +38,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable @observable private _function: boolean = false; @observable private _spaced: boolean = false; - @observable private _scriptKeys: any = Scripting.getGlobals(); - @observable private _scriptingDescriptions: any = Scripting.getDescriptions(); - @observable private _scriptingParams: any = Scripting.getParameters(); + @observable private _scriptKeys: any = ScriptingGlobals.getGlobals(); + @observable private _scriptingDescriptions: any = ScriptingGlobals.getDescriptions(); + @observable private _scriptingParams: any = ScriptingGlobals.getParameters(); @observable private _currWord: string = ""; @observable private _suggestions: string[] = []; @@ -224,9 +220,9 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable ScriptManager.Instance.addScript(this.dataDoc); - this._scriptKeys = Scripting.getGlobals(); - this._scriptingDescriptions = Scripting.getDescriptions(); - this._scriptingParams = Scripting.getParameters(); + this._scriptKeys = ScriptingGlobals.getGlobals(); + this._scriptingDescriptions = ScriptingGlobals.getDescriptions(); + this._scriptingParams = ScriptingGlobals.getParameters(); } // overlays document numbers (ex. d32) over all documents when clicked on @@ -577,8 +573,8 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable </div>; } - renderFuncListElement(value: string) { - return <div> + renderFuncListElement(value: string | object) { + return (typeof value !== "string") ? (null) : <div> <div style={{ fontSize: "14px" }}> {value} </div> diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx index 92d1f7446..b96977f32 100644 --- a/src/client/views/nodes/SliderBox.tsx +++ b/src/client/views/nodes/SliderBox.tsx @@ -2,30 +2,19 @@ import { runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider'; -import { documentSchema } from '../../../fields/documentSchemas'; -import { createSchema, makeInterface } from '../../../fields/Schema'; import { NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxBaseComponent } from '../DocComponent'; import { ScriptBox } from '../ScriptBox'; +import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import { Handle, Tick, TooltipRail, Track } from './SliderBox-components'; import './SliderBox.scss'; -import { StyleProp } from '../StyleProvider'; - -const SliderSchema = createSchema({ - _sliderMin: "number", - _sliderMax: "number", - _sliderMinThumb: "number", - _sliderMaxThumb: "number", -}); -type SliderDocument = makeInterface<[typeof SliderSchema, typeof documentSchema]>; -const SliderDocument = makeInterface(SliderSchema, documentSchema); @observer -export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocument>(SliderDocument) { +export class SliderBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SliderBox, fieldKey); } get minThumbKey() { return this.fieldKey + "-minThumb"; } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 56cb562bc..fafa39deb 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -3,11 +3,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from "mobx"; import { observer } from "mobx-react"; +import { basename } from "path"; import * as rp from 'request-promise'; import { Doc, DocListCast } from "../../../fields/Doc"; -import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; -import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { AudioField, VideoField } from "../../../fields/URLField"; import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from "../../../Utils"; @@ -36,7 +35,7 @@ type VideoDocument = makeInterface<[typeof documentSchema]>; const VideoDocument = makeInterface(documentSchema); @observer -export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, VideoDocument>(VideoDocument) { +export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); } static async convertDataUri(imageUri: string, returnedFilename: string) { try { @@ -189,7 +188,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } @action public Snapshot(downX?: number, downY?: number) { - const width = (this.layoutDoc._width || 0); + const width = NumCast(this.layoutDoc._width); const canvas = document.createElement('canvas'); canvas.width = 640; canvas.height = 640 * Doc.NativeHeight(this.layoutDoc) / (Doc.NativeWidth(this.layoutDoc) || 1); @@ -203,7 +202,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp if (!this._videoRef) { const b = Docs.Create.LabelDocument({ - x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 1), + x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y, 1), _width: 150, _height: 50, title: (this.layoutDoc._currentTimecode || 0).toString(), _isLinkButton: true }); @@ -224,7 +223,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, const retitled = StrCast(this.rootDoc.title).replace(/[ -\.]/g, ""); - const filename = path.basename(encodeURIComponent("snapshot" + retitled + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_"))); + const encodedFilename = encodeURIComponent("snapshot" + retitled + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_")); + const filename = basename(encodedFilename); VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && this.createRealSummaryLink(returnedFilename, downX, downY)); } @@ -232,12 +232,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => { const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath; - const width = this.layoutDoc._width || 1; - const height = this.layoutDoc._height || 0; + const width = NumCast(this.layoutDoc._width) || 1; + const height = NumCast(this.layoutDoc._height); const imageSummary = Docs.Create.ImageDocument(url, { _nativeWidth: Doc.NativeWidth(this.layoutDoc), _nativeHeight: Doc.NativeHeight(this.layoutDoc), - x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0), _isLinkButton: true, - _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc._currentTimecode || 0) + " image-" + x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y), _isLinkButton: true, + _width: 150, _height: height / width * 150, title: "--snapshot" + NumCast(this.layoutDoc._currentTimecode) + " image-" }); Doc.SetNativeWidth(Doc.GetProto(imageSummary), Doc.NativeWidth(this.layoutDoc)); Doc.SetNativeHeight(Doc.GetProto(imageSummary), Doc.NativeHeight(this.layoutDoc)); @@ -282,7 +282,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp // @ts-ignore // vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen); this._disposers.reactionDisposer?.(); - this._disposers.reactionDisposer = reaction(() => (this.layoutDoc._currentTimecode || 0), + this._disposers.reactionDisposer = reaction(() => NumCast(this.layoutDoc._currentTimecode), time => !this._playing && (vref.currentTime = time), { fireImmediately: true }); } } @@ -373,7 +373,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp const onYoutubePlayerReady = (event: any) => { this._disposers.reactionDisposer?.(); this._disposers.youtubeReactionDisposer?.(); - this._disposers.reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek((this.layoutDoc._currentTimecode || 0))); + this._disposers.reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek(NumCast(this.layoutDoc._currentTimecode))); this._disposers.youtubeReactionDisposer = reaction( () => CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true }); @@ -460,7 +460,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp this._youtubeIframeId = VideoBox._youtubeIframeCounter++; this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; const classname = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); - const start = untracked(() => Math.round((this.layoutDoc._currentTimecode || 0))); + const start = untracked(() => Math.round(NumCast(this.layoutDoc._currentTimecode))); return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`} onPointerLeave={this.updateTimecode} onLoad={this.youtubeIframeLoaded} className={classname} width={Doc.NativeWidth(this.layoutDoc) || 640} height={Doc.NativeHeight(this.layoutDoc) || 390} @@ -719,7 +719,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp style={{ pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined, borderRadius, - overflow: this.props.docViewPath?.().lastElement().fitWidth ? "auto" : undefined + overflow: this.props.docViewPath?.().slice(-1)[0].fitWidth ? "auto" : undefined }} onWheel={e => { e.stopPropagation(); e.preventDefault(); }}> <div className="videoBox-viewer" onPointerDown={this.marqueeDown} > <div style={{ @@ -755,7 +755,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp scrollTop={0} down={this._marqueeing} scaling={this.marqueeFitScaling} - docView={this.props.docViewPath().lastElement()} + docView={this.props.docViewPath().slice(-1)[0]} containerOffset={this.marqueeOffset} addDocument={this.addDocWithTimecode} finishMarquee={this.finishMarquee} diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index 79289abaa..7dc970496 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -5,6 +5,12 @@ height: 100%; position: relative; display: flex; + .webBox-background { + width: 100%; + height: 100%; + cursor: ew-resize; + background: lightGray; + } .webBox-ui { position: absolute; @@ -137,6 +143,7 @@ transform-origin: top left; width: 100%; height: 100%; + position: absolute; .webBox-htmlSpan { position: absolute; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 9956cc36b..7ff47107e 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -3,21 +3,20 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio import { observer } from "mobx-react"; import * as WebRequest from 'web-request'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; -import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { HtmlField } from "../../../fields/HtmlField"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; -import { listSpec, makeInterface } from "../../../fields/Schema"; +import { listSpec } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { WebField } from "../../../fields/URLField"; +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 { Docs } from "../../documents/Documents"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { KeyCodes } from "../../util/KeyCodes"; -import { Scripting } from "../../util/Scripting"; +import { ScriptingGlobals } from "../../util/ScriptingGlobals"; import { SnappingManager } from "../../util/SnappingManager"; import { undoBatch } from "../../util/UndoManager"; import { MarqueeOptionsMenu } from "../collections/collectionFreeForm"; @@ -36,18 +35,18 @@ import { StyleProp } from "../StyleProvider"; import { DocumentViewProps } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from "./LinkDocPreview"; +import { VideoBox } from "./VideoBox"; import "./WebBox.scss"; import React = require("react"); +const { CreateImage } = require("./WebBoxRenderer"); const _global = (window /* browser */ || global /* node */) as any; const htmlToText = require("html-to-text"); -type WebDocument = makeInterface<[typeof documentSchema]>; -const WebDocument = makeInterface(documentSchema); - @observer -export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, WebDocument>(WebDocument) { +export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } public static openSidebarWidth = 250; + public static sidebarResizerWidth = 5; private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _outerRef: React.RefObject<HTMLDivElement> = React.createRef(); @@ -63,6 +62,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable private _searching: boolean = false; @observable private _showSidebar = false; @observable private _scrollTimer: any; + @observable private _webPageHasBeenRendered = false; @observable private _overlayAnnoInfo: Opt<Doc>; @observable private _marqueeing: number[] | undefined; @observable private _isAnnotating = false; @@ -76,6 +76,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); } @computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; } + @computed get webThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; } constructor(props: any) { super(props); @@ -103,7 +104,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return true; } async componentDidMount() { - this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. + this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons) runInAction(() => { this._annotationKeySuffix = () => this._urlHash + "-annotations"; @@ -111,6 +112,48 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`); this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`); }); + reaction(() => this.props.isSelected(), + async (selected) => { + if (selected) { + this._webPageHasBeenRendered = true; + setTimeout(action(() => { + this._scrollHeight = Math.max(this.scrollHeight, this._iframe?.contentDocument?.body.scrollHeight || 0); + if (this._initialScroll !== undefined && this._outerRef.current) { + setTimeout(() => { + this._outerRef.current!.scrollTop = this._initialScroll!; + this._initialScroll = undefined; + }); + } + })); + } else if (!this.props.isContentActive() && + !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && /// don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty + LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty. + const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href; + if (this._iframe && !imageBitmap) { + var htmlString = this._iframe.contentDocument && new XMLSerializer().serializeToString(this._iframe.contentDocument); + if (!htmlString) { + htmlString = await (await fetch(Utils.CorsProxy(this.webField!.href))).text(); + } + this.layoutDoc.thumb = undefined; + const nativeWidth = NumCast(this.layoutDoc.nativeWidth); + CreateImage( + this._webUrl.endsWith("/") ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, + this._iframe.contentDocument?.styleSheets ?? [], + htmlString, + nativeWidth, + nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), + NumCast(this.layoutDoc._scrollTop) + ).then + ((dataUrl: any) => { + VideoBox.convertDataUri(dataUrl, this.layoutDoc[Id] + "-thumb" + (new Date()).getTime(), true).then( + returnedfilename => setTimeout(action(() => this.layoutDoc.thumb = new ImageField(returnedfilename)), 500)); + }) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); + } + } + }); this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight, autoHeight => { @@ -292,12 +335,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps iframe.contentDocument.addEventListener("pointerdown", this.iframeDown); this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight); setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000); - const initialScroll = this._initialScroll; - if (initialScroll !== undefined && this._outerRef.current) { - // bcz: not sure why this happens, but if the webpage isn't ready yet, it's scroll height seems to be limited. So we need to wait tp set scroll location to what we want. - setTimeout(() => this._outerRef.current!.scrollTop = initialScroll); - this._initialScroll = undefined; - } iframe.setAttribute("enable-annotation", "true"); iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => { let href = ""; @@ -353,42 +390,46 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } - @action - forward = () => { + forward = (checkAvailable?: boolean) => { const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []); const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []); - if (future.length) { - const curUrl = this._url; - this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); - if (this._webUrl === this._url) { - this._webUrl = curUrl; - setTimeout(action(() => this._webUrl = this._url)); - } else { - this._webUrl = this._url; + if (checkAvailable) return future.length; + runInAction(() => { + if (future.length) { + const curUrl = this._url; + this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]); + this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } + return true; } - return true; - } + }); return false; } - @action - back = () => { + back = (checkAvailable?: boolean) => { const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string")); const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []); - if (history.length) { - const curUrl = this._url; - if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]); - else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]); - this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); - if (this._webUrl === this._url) { - this._webUrl = curUrl; - setTimeout(action(() => this._webUrl = this._url)); - } else { - this._webUrl = this._url; + if (checkAvailable) return history.length; + runInAction(() => { + if (history.length) { + const curUrl = this._url; + if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]); + else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]); + this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } + return true; } - return true; - } + }); return false; } @@ -452,8 +493,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <button className="submitUrl" onClick={() => this.submitURL(this._keyInput.current!.value)} onDragOver={e => e.stopPropagation()} onDrop={this.onWebUrlDrop}> GO </button> - <button className="submitUrl" onClick={this.back}> <FontAwesomeIcon icon="caret-left" size="lg" /> </button> - <button className="submitUrl" onClick={this.forward}> <FontAwesomeIcon icon="caret-right" size="lg" /> </button> + <button className="submitUrl" onClick={() => this.back}> <FontAwesomeIcon icon="caret-left" size="lg" /> </button> + <button className="submitUrl" onClick={() => this.forward}> <FontAwesomeIcon icon="caret-right" size="lg" /> </button> </div> </div> ); @@ -512,7 +553,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } @computed get urlContent() { - if (this._hackHide) return (null); + if (this._hackHide || (this.webThumb && (!this._webPageHasBeenRendered && LightboxView.LightboxDoc !== this.rootDoc))) return (null); const field = this.dataDoc[this.props.fieldKey]; let view; if (field instanceof HtmlField) { @@ -530,6 +571,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps style={{ pointerEvents: this._scrollTimer ? "none" : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={"https://crossorigin.me/https://cs.brown.edu"} />; } + setTimeout(action(() => this._webPageHasBeenRendered = true)); return view; } @@ -542,34 +584,37 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (!this.layoutDoc._showSidebar) this.toggleSidebar(); return this.addDocumentWrapper(doc, sidebarKey); } - sidebarBtnDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, (e, down, delta) => { + @observable _draggingSidebar = false; + sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf + setupMoveUpEvents(this, e, action((e, down, delta) => { + this._draggingSidebar = true; const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; + const ratio = (curNativeWidth + (onButton ? 1 : -1) * localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; if (ratio >= 1) { this.layoutDoc.nativeWidth = nativeWidth * ratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; + onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]); this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } return false; - }, emptyFunction, () => this.toggleSidebar()); + }), action(() => this._draggingSidebar = false), () => this.toggleSidebar()); } @observable _previewNativeWidth: Opt<number> = undefined; @observable _previewWidth: Opt<number> = undefined; toggleSidebar = action((preview: boolean = false) => { const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); - const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; + const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; + const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth + WebBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth; const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); if (preview) { - this._previewNativeWidth = nativeWidth * ratio; - this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth; + this._previewNativeWidth = nativeWidth * sideratio; + this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * sideratio / curNativeWidth; this._showSidebar = true; } else { - this.layoutDoc.nativeWidth = nativeWidth * ratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * ratio / curNativeWidth; + this.layoutDoc.nativeWidth = nativeWidth * pdfratio; + this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * pdfratio / curNativeWidth; this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } }); @@ -633,7 +678,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } return this.props.styleProvider?.(doc, props, property); } - pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none"; + pointerEvents = () => !this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none"; render() { const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents ? this.props.pointerEvents as any : undefined; const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; @@ -664,13 +709,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />; return ( <div className="webBox" ref={this._mainCont} - style={{ pointerEvents: this.pointerEvents() }} > - <div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}> + style={{ pointerEvents: this.pointerEvents(), display: this.props.thumbShown?.() ? "none" : undefined }} > + <div className="webBox-background" onPointerDown={e => this.sidebarBtnDown(e, false)} /> + <div className="webBox-container" style={{ + position: "absolute", + width: `calc(${100 / scale}% - ${(this.sidebarWidth() + WebBox.sidebarResizerWidth) / scale * (this._previewWidth ? scale : 1)}px)`, + transform: `scale(${scale})`, + pointerEvents + }} onContextMenu={this.specificContextMenu}> <div className={"webBox-outerContent"} ref={this._outerRef} style={{ - width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`, height: `${100 / scale}%`, - transform: `scale(${scale})`, pointerEvents }} onWheel={e => { e.stopPropagation(); e.preventDefault(); }} // block wheel events from propagating since they're handled by the iframe @@ -703,6 +752,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </div > <SidebarAnnos ref={this._sidebarRef} {...this.props} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} fieldKey={this.fieldKey + "-" + this._urlHash} rootDoc={this.rootDoc} layoutDoc={this.layoutDoc} @@ -720,11 +770,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps top: StrCast(this.rootDoc._showTitle) === "title" ? 20 : 5, backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK }} - onPointerDown={this.sidebarBtnDown} > + onPointerDown={e => this.sidebarBtnDown(e, true)} > <FontAwesomeIcon style={{ color: Colors.WHITE }} icon={"comment-alt"} size="sm" /> </div> {!this.props.isContentActive() ? (null) : this.searchUI} </div>); } } -Scripting.addGlobal(function urlHash(url: string) { return WebBox.urlHash(url); });
\ No newline at end of file +ScriptingGlobals.add(function urlHash(url: string) { return WebBox.urlHash(url); });
\ No newline at end of file diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js new file mode 100644 index 000000000..08a5746d1 --- /dev/null +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -0,0 +1,395 @@ +/** + * + * @param {StyleSheetList} styleSheets + */ +var ForeignHtmlRenderer = function (styleSheets) { + + const self = this; + + /** + * + * @param {String} binStr + */ + const binaryStringToBase64 = function (binStr) { + return new Promise(function (resolve) { + const reader = new FileReader(); + reader.readAsDataURL(binStr); + reader.onloadend = function () { + resolve(reader.result); + } + }); + }; + + function prepend(extension) { + return window.location.origin + extension; + } + function CorsProxy(url) { + return prepend("/corsProxy/") + encodeURIComponent(url); + } + /** + * + * @param {String} url + * @returns {Promise} + */ + const getResourceAsBase64 = function (webUrl, inurl) { + return new Promise(function (resolve, reject) { + const xhr = new XMLHttpRequest(); + //const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl; + //const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl; + var url = inurl; + if (inurl.startsWith("/static")) { + url = (new URL(webUrl).origin + inurl); + } else + if ((inurl.startsWith("/") && !inurl.startsWith("//"))) { + url = CorsProxy(new URL(webUrl).origin + inurl); + } else if (!inurl.startsWith("http") && !inurl.startsWith("//")) { + url = CorsProxy(webUrl + "/" + inurl); + } + xhr.open("GET", url); + xhr.responseType = 'blob'; + + xhr.onreadystatechange = async function () { + if (xhr.readyState === 4 && xhr.status === 200) { + const resBase64 = await binaryStringToBase64(xhr.response); + + resolve( + { + "resourceUrl": inurl, + "resourceBase64": resBase64 + } + ); + } else if (xhr.readyState === 4) { + console.log("COULDN'T FIND: " + (inurl.startsWith("/") ? webUrl + inurl : inurl)); + resolve( + { + "resourceUrl": "", + "resourceBase64": inurl + } + ); + } + }; + + xhr.send(null); + }); + }; + + /** + * + * @param {String[]} urls + * @returns {Promise} + */ + const getMultipleResourcesAsBase64 = function (webUrl, urls) { + const promises = []; + for (let i = 0; i < urls.length; i++) { + promises.push(getResourceAsBase64(webUrl, urls[i])); + } + return Promise.all(promises); + }; + + /** + * + * @param {String} str + * @param {Number} startIndex + * @param {String} prefixToken + * @param {String[]} suffixTokens + * + * @returns {String|null} + */ + const parseValue = function (str, startIndex, prefixToken, suffixTokens) { + const idx = str.indexOf(prefixToken, startIndex); + if (idx === -1) { + return null; + } + + let val = ''; + for (let i = idx + prefixToken.length; i < str.length; i++) { + if (suffixTokens.indexOf(str[i]) !== -1) { + break; + } + + val += str[i]; + } + + return { + "foundAtIndex": idx, + "value": val + } + }; + + /** + * + * @param {String} cssRuleStr + * @returns {String[]} + */ + const getUrlsFromCssString = function (cssRuleStr, selector = "url(", delimiters = [')'], mustEndWithQuote = false) { + const urlsFound = []; + let searchStartIndex = 0; + + while (true) { + const url = parseValue(cssRuleStr, searchStartIndex, selector, delimiters); + if (url === null) { + break; + } + searchStartIndex = url.foundAtIndex + url.value.length; + if (mustEndWithQuote && url.value[url.value.length - 1] !== '"') continue; + const unquoted = removeQuotes(url.value); + if (!unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") )*/ || unquoted === 'http://' || unquoted === 'https://') { + continue; + } + + unquoted && urlsFound.push(unquoted); + } + + return urlsFound; + }; + + /** + * + * @param {String} html + * @returns {String[]} + */ + const getImageUrlsFromFromHtml = function (html) { + return getUrlsFromCssString(html, "src=", [' ', '>', '\t'], true); + }; + const getSourceUrlsFromFromHtml = function (html) { + return getUrlsFromCssString(html, "source=", [' ', '>', '\t'], true); + }; + + /** + * + * @param {String} str + * @returns {String} + */ + const removeQuotes = function (str) { + return str.replace(/["']/g, ""); + }; + + const escapeRegExp = function (string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + }; + + /** + * + * @param {String} contentHtml + * @param {Number} width + * @param {Number} height + * + * @returns {Promise<String>} + */ + const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll) { + + return new Promise(async function (resolve, reject) { + + /* !! The problems !! + * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with) + * 2. Platform won't wait for external assets to load (fonts, images, etc.) + */ + + // copy styles + let cssStyles = ""; + let urlsFoundInCss = []; + + for (let i = 0; i < styleSheets.length; i++) { + try { + const rules = styleSheets[i].cssRules + for (let j = 0; j < rules.length; j++) { + const cssRuleStr = rules[j].cssText; + urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr)); + cssStyles += cssRuleStr; + } + } catch (e) { + + } + } + + // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss); + // for (let i = 0; i < fetchedResourcesFromStylesheets.length; i++) { + // const r = fetchedResourcesFromStylesheets[i]; + // if (r.resourceUrl) { + // cssStyles = cssStyles.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64); + // } + // } + + contentHtml = contentHtml.replace(/<source[^>]*>/g, "") // <picture> tags have a <source> which has a srcset field of image refs. instead of converting each, just use the default <img> of the picture + .replace(/noscript/g, "div").replace(/<div class="mediaset"><\/div>/g, "") // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag + .replace(/<link[^>]*>/g, "") // don't need to keep any linked style sheets because we've already processed all style sheets above + .replace(/srcset="([^ "]*)[^"]*"/g, "src=\"$1\""); // instead of converting each item in the srcset to a data url, just convert the first one and use that + let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml); + const fetchedResources = await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml); + for (let i = 0; i < fetchedResources.length; i++) { + const r = fetchedResources[i]; + if (r.resourceUrl) { + contentHtml = contentHtml.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64); + } + } + + const styleElem = document.createElement("style"); + styleElem.innerHTML = cssStyles.replace(">", ">").replace("<", "<"); + + const styleElemString = new XMLSerializer().serializeToString(styleElem).replace(/>/g, ">").replace(/</g, "<"); + + // create DOM element string that encapsulates styles + content + const contentRootElem = document.createElement("body"); + contentRootElem.style.zIndex = "1111"; + // contentRootElem.style.transform = "scale(0.08)" + contentRootElem.innerHTML = styleElemString + contentHtml; + contentRootElem.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); + //document.body.appendChild(contentRootElem); + + const contentRootElemString = new XMLSerializer().serializeToString(contentRootElem); + + // build SVG string + const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}'> + <foreignObject x='0' y='${-scroll}' width='${width}' height='${scroll + height}'> + ${contentRootElemString} + </foreignObject> + </svg>`; + + // convert SVG to data-uri + const dataUri = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svg)))}`; + + resolve(dataUri); + }); + }; + + /** + * @param {String} html + * @param {Number} width + * @param {Number} height + * + * @return {Promise<Image>} + */ + this.renderToImage = async function (webUrl, html, width, height, scroll) { + return new Promise(async function (resolve, reject) { + const img = new Image(); + console.log("BUILDING SVG for:" + webUrl); + img.src = await buildSvgDataUri(webUrl, html, width, height, scroll); + + img.onload = function () { + console.log("IMAGE SVG created:" + webUrl); + resolve(img); + }; + }); + }; + + /** + * @param {String} html + * @param {Number} width + * @param {Number} height + * + * @return {Promise<Image>} + */ + this.renderToCanvas = async function (webUrl, html, width, height, scroll) { + return new Promise(async function (resolve, reject) { + const img = await self.renderToImage(webUrl, html, width, height, scroll); + + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + + const canvasCtx = canvas.getContext('2d'); + canvasCtx.drawImage(img, 0, 0, img.width, img.height); + + resolve(canvas); + }); + }; + + /** + * @param {String} html + * @param {Number} width + * @param {Number} height + * + * @return {Promise<String>} + */ + this.renderToBase64Png = async function (webUrl, html, width, height, scroll) { + return new Promise(async function (resolve, reject) { + const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll); + resolve(canvas.toDataURL('image/png')); + }); + }; + +}; + + +export function CreateImage(webUrl, styleSheets, html, width, height, scroll) { + const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll); + return val; +} + + + +var ClipboardUtils = new function () { + var permissions = { + 'image/bmp': true, + 'image/gif': true, + 'image/png': true, + 'image/jpeg': true, + 'image/tiff': true + }; + + function getType(types) { + for (var j = 0; j < types.length; ++j) { + var type = types[j]; + if (permissions[type]) { + return type; + } + } + return null; + } + function getItem(items) { + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + if (item) { + var type = getType(item.types); + if (type) { + return item.getType(type); + } + } + } + return null; + } + function loadFile(file, callback) { + if (window.FileReader) { + var reader = new FileReader(); + reader.onload = function () { + callback(reader.result, null); + }; + reader.onerror = function () { + callback(null, 'Incorrect file.'); + }; + reader.readAsDataURL(file); + } else { + callback(null, 'File api is not supported.'); + } + } + this.readImage = function (callback) { + if (navigator.clipboard) { + var promise = navigator.clipboard.read(); + promise + .then(function (items) { + var promise = getItem(items); + if (promise == null) { + callback(null, null); + return; + } + promise + .then(function (result) { + loadFile(result, callback); + }) + .catch(function (error) { + callback(null, 'Reading clipboard error.'); + }); + }) + .catch(function (error) { + callback(null, 'Reading clipboard error.'); + }); + } else { + callback(null, 'Clipboard is not supported.'); + } + }; +}; + + +export function pasteImageBitmap(callback) { + return ClipboardUtils.readImage(callback); +}
\ No newline at end of file diff --git a/src/client/views/nodes/button/ButtonScripts.ts b/src/client/views/nodes/button/ButtonScripts.ts index bb4dd8bc9..f3731b8f9 100644 --- a/src/client/views/nodes/button/ButtonScripts.ts +++ b/src/client/views/nodes/button/ButtonScripts.ts @@ -1,14 +1,14 @@ -import { Scripting } from "../../../util/Scripting"; +import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; import { SelectionManager } from "../../../util/SelectionManager"; // toggle: Set overlay status of selected document -Scripting.addGlobal(function changeView(view: string) { +ScriptingGlobals.add(function changeView(view: string) { const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; selected ? selected.Document._viewType = view : console.log("[FontIconBox.tsx] changeView failed"); }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function toggleOverlay() { +ScriptingGlobals.add(function toggleOverlay() { const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("failed"); });
\ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index bd103dcf7..ca13590de 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -5,31 +5,30 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorState, SketchPicker } from 'react-color'; -import { Doc, StrListCast, WidthSym, HeightSym } from '../../../../fields/Doc'; +import { Doc, StrListCast } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; -import { createSchema, makeInterface } from '../../../../fields/Schema'; +import { createSchema } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { WebField } from '../../../../fields/URLField'; +import { Utils } from '../../../../Utils'; import { DocumentType } from '../../../documents/DocumentTypes'; -import { Scripting } from "../../../util/Scripting"; +import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; import { SelectionManager } from '../../../util/SelectionManager'; -import { UndoManager, undoBatch } from '../../../util/UndoManager'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionViewType } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; import { DocComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { GestureOverlay } from '../../GestureOverlay'; import { Colors } from '../../global/globalEnums'; -import { SetActiveInkColor, ActiveFillColor, SetActiveFillColor, ActiveInkWidth, ActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; +import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { StyleProp } from '../../StyleProvider'; import { FieldView, FieldViewProps } from '.././FieldView'; import { RichTextMenu } from '../formattedText/RichTextMenu'; -import { Utils } from '../../../../Utils'; -import { IButtonProps } from './ButtonInterface'; +import { WebBox } from '../WebBox'; import { FontIconBadge } from './FontIconBadge'; import './FontIconBox.scss'; -import { WebBox } from '../WebBox'; const FontIconSchema = createSchema({ icon: "string", }); @@ -57,11 +56,8 @@ export enum NumButtonType { export interface ButtonProps extends FieldViewProps { type?: ButtonType; } - -type FontIconDocument = makeInterface<[typeof FontIconSchema]>; -const FontIconDocument = makeInterface(FontIconSchema); @observer -export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(FontIconDocument) { +export class FontIconBox extends DocComponent<ButtonProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } showTemplate = (): void => { const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); @@ -105,16 +101,11 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon */ @computed get numberButton() { const numBtnType: string = StrCast(this.rootDoc.numBtnType); - const setValue = (value: number) => { - // Script for running the toggle - const script: string = StrCast(this.rootDoc.script) + "(" + value + ")"; - ScriptField.MakeScript(script)?.script.run(); - }; + const numScript = ScriptCast(this.rootDoc.script); + const setValue = (value: number) => numScript?.script.run({ value, _readOnly_: false }); // Script for checking the outcome of the toggle - const checkScript: string = StrCast(this.rootDoc.script) + "(0, true)"; - const checkResult: number = ScriptField.MakeScript(checkScript)?.script.run().result || 0; - + const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0; if (numBtnType === NumButtonType.Slider) { const dropdown = @@ -237,34 +228,37 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const script: string = StrCast(this.rootDoc.script); + const script = ScriptCast(this.rootDoc.script); let noviceList: string[] = []; let text: string | undefined; let dropdown = true; let icon: IconProp = "caret-down"; - - if (script === 'setView') { - const selected = SelectionManager.Docs().lastElement(); - if (selected) { - if (StrCast(selected.type) === DocumentType.COL) { - text = StrCast(selected._viewType); + try { + if (script.script.originalScript.startsWith('setView')) { + const selected = SelectionManager.Docs().lastElement(); + if (selected) { + if (StrCast(selected.type) === DocumentType.COL) { + text = StrCast(selected._viewType); + } else { + dropdown = false; + text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type); + icon = Doc.toIcon(selected); + } } else { dropdown = false; - text = selected.type === DocumentType.RTF ? "Text" : StrCast(selected.type); - icon = Doc.toIcon(selected); + icon = "globe-asia"; + text = "User Default"; } - } else { - dropdown = false; - icon = "globe-asia"; - text = "User Default"; + noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; + } else if (script.script.originalScript.startsWith('setFont')) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; + text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", + "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; } - noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking]; - } else if (script === 'setFont') { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - noviceList = ["Roboto", "Times New Roman", "Arial", "Georgia", - "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; + } catch (e) { + console.log(e); } // Get items to place into the list @@ -272,18 +266,12 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) { return; } - const click = () => { - const s = ScriptField.MakeScript(script + '("' + value + '")'); - if (s) { - s.script.run().result; - } - }; return <div className="list-item" key={`${value}`} style={{ - fontFamily: script === 'setFont' ? value : undefined, + fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined, backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined }} - onClick={click}> + onClick={() => script.script.run({ value }).result}> {value[0].toUpperCase() + value.slice(1)} </div>; }); @@ -319,15 +307,12 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon } @observable colorPickerClosed: boolean = true; - @computed get colorScript() { - const script = StrCast(this.rootDoc.script); - return ScriptField.MakeScript(script + '(colValue, checkResult)', { colValue: "string", checkResult: "boolean" }); - } + @computed get colorScript() { return ScriptCast(this.rootDoc.script); } colorPicker = (curColor: string) => { const change = (value: ColorState) => { const s = this.colorScript; - s && undoBatch(() => s.script.run({ colValue: Utils.colorString(value), checkResult: false }).result)(); + s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)(); }; const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', @@ -343,7 +328,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon @computed get colorButton() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const curColor = this.colorScript?.script.run({ colValue: undefined, checkResult: true }).result ?? "transparent"; + const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? "transparent"; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}> @@ -439,52 +424,34 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon @computed get editableText() { // Script for running the toggle - const script: string = StrCast(this.rootDoc.script); - - // Script for checking the outcome of the toggle - const checkScript: string = StrCast(this.rootDoc.script) + "('', true)"; - + const script = ScriptCast(this.rootDoc.script); // Function to run the script - const checkResult = ScriptField.MakeScript(checkScript)?.script.run().result; + const checkResult = script?.script.run({ value: "", _readOnly_: true }).result; - const setValue = (value: string, shiftDown?: boolean): boolean => { - ScriptField.MakeScript(script + "('" + value + "')")?.script.run(); - return true; - }; + const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result; return ( <div className="menuButton editableText"> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={"lock"} /> <div style={{ width: "calc(100% - .875em)", paddingLeft: "4px" }}> - <EditableView GetValue={() => checkResult} SetValue={setValue} contents={checkResult} /> + <EditableView GetValue={() => script?.script.run({ value: "", _readOnly_: true }).result} SetValue={setValue} contents={checkResult} /> </div> </div> ); } - render() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : - <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor, position: "absolute" }}> + <div className="fontIconBox-label" style={{ color, backgroundColor, position: "absolute" }}> {this.label} </div>; const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) : - <div className="fontIconBox-label" style={{ color: color, backgroundColor: "transparent" }}> + <div className="fontIconBox-label" style={{ color, backgroundColor: "transparent" }}> {this.label} </div>; - const buttonProps: IButtonProps = { - type: this.type, - rootDoc: this.rootDoc, - label: label, - backgroundColor: backgroundColor, - icon: this.icon, - color: color - }; - const buttonText = StrCast(this.rootDoc.buttonText); // TODO:glr Add label of button type @@ -493,7 +460,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon switch (this.type) { case ButtonType.TextButton: button = ( - <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}> + <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} /> {buttonText ? <div className="button-text"> @@ -522,7 +489,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon break; case ButtonType.ToolButton: button = ( - <div className={`menuButton ${this.type}`} style={{ opacity: 1, backgroundColor: backgroundColor, color: color }}> + <div className={`menuButton ${this.type}`} style={{ opacity: 1, backgroundColor, color }}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} /> {label} </div> @@ -534,7 +501,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon break; case ButtonType.ClickButton: button = ( - <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor, opacity: 1 }}> + <div className={`menuButton ${this.type}`} style={{ color, backgroundColor, opacity: 1 }}> <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} /> {label} </div> @@ -544,7 +511,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon const trailsIcon = <img src={`/assets/${"presTrails.png"}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? "0%" : "100%"})` }} />; button = ( - <div className={`menuButton ${this.type}`} style={{ color: color, backgroundColor: backgroundColor }}> + <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}> {this.icon === "pres-trail" ? trailsIcon : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={this.icon} color={color} />} {menuLabel} <FontIconBadge collection={Cast(this.rootDoc.watchedDocuments, Doc, null)} /> @@ -564,14 +531,14 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon // toggle: Set overlay status of selected document -Scripting.addGlobal(function setView(view: string) { +ScriptingGlobals.add(function setView(view: string) { const selected = SelectionManager.Docs().lastElement(); selected ? selected._viewType = view : console.log("[FontIconBox.tsx] changeView failed"); }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: boolean) { +ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); if (checkResult) { return selected?._backgroundColor ?? "transparent"; @@ -580,7 +547,7 @@ Scripting.addGlobal(function setBackgroundColor(color?: string, checkResult?: bo }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function setHeaderColor(color?: string, checkResult?: boolean) { +ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { if (checkResult) { return Doc.SharingDoc().userColor; } @@ -590,7 +557,7 @@ Scripting.addGlobal(function setHeaderColor(color?: string, checkResult?: boolea }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { +ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; if (checkResult && selected) { if (NumCast(selected.Document.z) >= 1) return Colors.MEDIUM_BLUE; @@ -612,7 +579,7 @@ Scripting.addGlobal(function toggleOverlay(checkResult?: boolean) { **/ // toggle: Set overlay status of selected document -Scripting.addGlobal(function setFont(font: string, checkResult?: boolean) { +ScriptingGlobals.add(function setFont(font: string, checkResult?: boolean) { SelectionManager.Docs().map(doc => doc._fontFamily = font); const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { @@ -622,7 +589,7 @@ Scripting.addGlobal(function setFont(font: string, checkResult?: boolean) { else Doc.UserDoc().fontFamily = font; }); -Scripting.addGlobal(function getActiveTextInfo(info: "family" | "size" | "color" | "highlight") { +ScriptingGlobals.add(function getActiveTextInfo(info: "family" | "size" | "color" | "highlight") { const editorView = RichTextMenu.Instance.TextView?.EditorView; const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection(); switch (info) { @@ -633,7 +600,7 @@ Scripting.addGlobal(function getActiveTextInfo(info: "family" | "size" | "color" } }); -Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", checkResult?: boolean) { +ScriptingGlobals.add(function setAlignment(align: "left" | "right" | "center", checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return (editorView ? RichTextMenu.Instance.textAlign : Doc.UserDoc().textAlign) === align ? Colors.MEDIUM_BLUE : "transparent"; @@ -642,7 +609,7 @@ Scripting.addGlobal(function setAlignment(align: "left" | "right" | "center", ch else Doc.UserDoc().textAlign = align; }); -Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", checkResult?: boolean) { +ScriptingGlobals.add(function setBulletList(mapStyle: "bullet" | "decimal", checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle(); @@ -657,8 +624,8 @@ Scripting.addGlobal(function setBulletList(mapStyle: "bullet" | "decimal", check }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) { - const editorView = RichTextMenu.Instance.TextView?.EditorView; +ScriptingGlobals.add(function setFontColor(color?: string, checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return editorView ? RichTextMenu.Instance.fontColor : Doc.UserDoc().fontColor; @@ -669,7 +636,7 @@ Scripting.addGlobal(function setFontColor(color?: string, checkResult?: boolean) }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: boolean) { +ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); const editorView = RichTextMenu.Instance.TextView?.EditorView; @@ -686,18 +653,18 @@ Scripting.addGlobal(function setFontHighlight(color?: string, checkResult?: bool }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function setFontSize(size: string | number, checkResult?: boolean) { - if (typeof size === "number") size = size.toString(); - if (size && Number(size).toString() === size) size += "px"; - const editorView = RichTextMenu.Instance.TextView?.EditorView; +ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return (editorView ? RichTextMenu.Instance.fontSize : StrCast(Doc.UserDoc().fontSize, "10px")).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; }); -Scripting.addGlobal(function toggleBold(checkResult?: boolean) { +ScriptingGlobals.add(function toggleBold(checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === "bold") ? Colors.MEDIUM_BLUE : "transparent"; @@ -706,7 +673,7 @@ Scripting.addGlobal(function toggleBold(checkResult?: boolean) { else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === "bold" ? undefined : "bold"; }); -Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) { +ScriptingGlobals.add(function toggleUnderline(checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === "underline") ? Colors.MEDIUM_BLUE : "transparent"; @@ -715,7 +682,7 @@ Scripting.addGlobal(function toggleUnderline(checkResult?: boolean) { else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === "underline" ? undefined : "underline"; }); -Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { +ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === "italics") ? Colors.MEDIUM_BLUE : "transparent"; @@ -733,7 +700,7 @@ Scripting.addGlobal(function toggleItalic(checkResult?: boolean) { * setStrokeColor **/ -Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolean) { +ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) { if (checkResult) { return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ? Colors.MEDIUM_BLUE : "transparent"; @@ -759,7 +726,7 @@ Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolea }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function setFillColor(color?: string, checkResult?: boolean) { +ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); if (checkResult) { if (selected?.type === DocumentType.INK) { @@ -771,7 +738,7 @@ Scripting.addGlobal(function setFillColor(color?: string, checkResult?: boolean) SelectionManager.Docs().filter(doc => doc.type === DocumentType.INK).map(doc => doc.fillColor = color); }); -Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean) { +ScriptingGlobals.add(function setStrokeWidth(width: number, checkResult?: boolean) { if (checkResult) { const selected = SelectionManager.Docs().lastElement(); if (selected?.type === DocumentType.INK) { @@ -784,7 +751,7 @@ Scripting.addGlobal(function setStrokeWidth(width: number, checkResult?: boolean }); // toggle: Set overlay status of selected document -Scripting.addGlobal(function setStrokeColor(color?: string, checkResult?: boolean) { +ScriptingGlobals.add(function setStrokeColor(color?: string, checkResult?: boolean) { if (checkResult) { const selected = SelectionManager.Docs().lastElement(); if (selected?.type === DocumentType.INK) { @@ -800,7 +767,7 @@ Scripting.addGlobal(function setStrokeColor(color?: string, checkResult?: boolea /** WEB * webSetURL **/ -Scripting.addGlobal(function webSetURL(url: string, checkResult?: boolean) { +ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) { const selected = SelectionManager.Views().lastElement(); if (selected?.rootDoc.type === DocumentType.WEB) { if (checkResult) { @@ -810,24 +777,26 @@ Scripting.addGlobal(function webSetURL(url: string, checkResult?: boolean) { //selected.rootDoc.data = new WebField(url); } }); -Scripting.addGlobal(function webForward() { - const selected = SelectionManager.Views().lastElement(); - if (selected?.rootDoc.type === DocumentType.WEB) { - (selected.ComponentView as WebBox).forward(); +ScriptingGlobals.add(function webForward(checkResult?: boolean) { + const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox); + if (checkResult) { + return selected?.forward(checkResult) ? undefined : "lightGray"; } + selected?.forward(); }); -Scripting.addGlobal(function webBack() { - const selected = SelectionManager.Views().lastElement(); - if (selected?.rootDoc.type === DocumentType.WEB) { - (selected.ComponentView as WebBox).back(); +ScriptingGlobals.add(function webBack(checkResult?: boolean) { + const selected = (SelectionManager.Views().lastElement()?.ComponentView as WebBox); + if (checkResult) { + return selected?.back(checkResult) ? undefined : "lightGray"; } + selected?.back(); }); /** Schema * toggleSchemaPreview **/ -Scripting.addGlobal(function toggleSchemaPreview(checkResult?: boolean) { +ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); if (checkResult && selected) { const result: boolean = NumCast(selected.schemaPreviewWidth) > 0; @@ -846,7 +815,7 @@ Scripting.addGlobal(function toggleSchemaPreview(checkResult?: boolean) { /** STACK * groupBy */ -Scripting.addGlobal(function setGroupBy(key: string, checkResult?: boolean) { +ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) { SelectionManager.Docs().map(doc => doc._fontFamily = key); const editorView = RichTextMenu.Instance.TextView?.EditorView; if (checkResult) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 311d52afa..6192b6829 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -12,13 +12,11 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc"; -import { documentSchema } from '../../../../fields/documentSchemas'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; -import { makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils'; @@ -77,13 +75,10 @@ export interface FormattedTextBoxProps { } export const GoogleRef = "googleDocId"; -type RichTextDocument = makeInterface<[typeof documentSchema]>; -const RichTextDocument = makeInterface(documentSchema); - type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { +export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps)>() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; @@ -645,7 +640,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } else if (!this.rootDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); this.rootDoc.title = "text"; - this.rootDoc.layout = (this.layoutDoc as Doc).layout as string; + this.rootDoc.layout = this.layoutDoc.layout as string; this.rootDoc.title = this.layoutDoc.isTemplateForField as string; this.rootDoc.isTemplateDoc = false; this.rootDoc.isTemplateForField = ""; @@ -1130,13 +1125,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, dispatchTransaction: this.dispatchTransaction, nodeViews: { - dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, - dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, - dashField(node, view, getPos) { return new DashFieldView(node, view, getPos, self); }, - equation(node, view, getPos) { return new EquationView(node, view, getPos, self); }, - summary(node, view, getPos) { return new SummaryView(node, view, getPos); }, - ordered_list(node, view, getPos) { return new OrderedListView(); }, - footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } + dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, + dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); }, + dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, + equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, + summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, + ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); }, + footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); } }, clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, @@ -1423,7 +1418,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0]; }), 1000); }); - } catch (e) { console.log(e.message); } + } catch (e: any) { console.log(e.message); } this._lastText = curText; } } diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 76a5675de..c76eda859 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -106,7 +106,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind("Mod-U", toggleEditableMark(schema.marks.underline)); //Commands for lists - bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state, dispatch as any)); + bind("Ctrl-i", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { /// bcz; Argh!! replace layotuTEmpalteString with a onTab prop conditionally handles Tab); @@ -125,7 +125,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey dispatch(tx3); })) { // couldn't sink into an existing list, so wrap in a new one const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); - if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => { + if (!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => { const tx3 = updateBullets(tx2, schema); // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx3.ensureMarks([...marks]); @@ -160,14 +160,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey }); //Commands to modify BlockType - bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state, dispatch as any))); - bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state, dispatch as any)); - bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state, dispatch as any)); + bind("Ctrl->", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any))); + bind("Alt-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any)); + bind("Shift-Ctrl-\\", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any)); bind("Ctrl-m", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() })))); for (let i = 1; i <= 6; i++) { - bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state, dispatch as any)); + bind("Shift-Ctrl-" + i, (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any)); } //Command to create a horizontal break line @@ -197,13 +197,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind("Backspace", (state: EditorState<S>, dispatch: (tx: Transaction<Schema<any, any>>) => void) => { if (!canEdit(state)) return true; - if (!deleteSelection(state, (tx: Transaction<Schema<any, any>>) => { + if (!deleteSelection(state, (tx: Transaction<S>) => { dispatch(updateBullets(tx, schema)); })) { - if (!joinBackward(state, (tx: Transaction<Schema<any, any>>) => { + if (!joinBackward(state, (tx: Transaction<S>) => { dispatch(updateBullets(tx, schema)); })) { - if (!selectNodeBackward(state, (tx: Transaction<Schema<any, any>>) => { + if (!selectNodeBackward(state, (tx: Transaction<S>) => { dispatch(updateBullets(tx, schema)); })) { return false; @@ -225,14 +225,14 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey const depth = trange ? liftTarget(trange) : undefined; const split = path.length > 5 && !path[path.length - 3].textContent && path[path.length - 6].type !== schema.nodes.list_item; if (split && trange && depth !== undefined && depth !== null) { - dispatch(state.tr.lift(trange, depth)); + dispatch(state.tr.lift(trange, depth) as any); return true; } const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); const cr = state.selection.$from.node().textContent.endsWith("\n"); - if (cr || !newlineInCode(state, dispatch)) { - if (!splitListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { + if (cr || !newlineInCode(state, dispatch as any)) { + if (!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => { const tx3 = updateBullets(tx2, schema); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 711136469..bafae84dc 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -34,13 +34,9 @@ export class RichTextRules { wrappingInputRule( /^1\.\s$/, schema.nodes.ordered_list, - () => { - return ({ mapStyle: "decimal", bulletStyle: 1 }); - }, - (match: any, node: any) => { - return node.childCount + node.attrs.order === +match[1]; - }, - (type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } }) + () => ({ mapStyle: "decimal", bulletStyle: 1 }), + (match: any, node: any) => node.childCount + node.attrs.order === +match[1], + ((type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })) as any ), // A. create alphabetical ordered list @@ -55,20 +51,16 @@ export class RichTextRules { (match: any, node: any) => { return node.childCount + node.attrs.order === +match[1]; }, - (type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } }) + ((type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } })) as any ), // * + - create bullet list wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.ordered_list, // match => { - () => { - return ({ mapStyle: "bullet" }); - // return ({ order: +match[1] }) - }, - (match: any, node: any) => { - return node.childCount + node.attrs.order === +match[1]; - }, - (type: any) => ({ type: type, attrs: { mapStyle: "bullet" } })), + () => ({ mapStyle: "bullet" }), // ({ order: +match[1] }) + (match: any, node: any) => node.childCount + node.attrs.order === +match[1], + ((type: any) => ({ type: type, attrs: { mapStyle: "bullet" } })) as any + ), // ``` create code block textblockTypeInputRule(/^```$/, schema.nodes.code_block), @@ -221,7 +213,7 @@ export class RichTextRules { tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote. return tr.setSelection(new NodeSelection( // select the footnote node to open its display tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) - tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))); + tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0)))); }), // activate a style by name using prefix '%<color name>' diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 2370b7e16..2e312ee51 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -6,11 +6,10 @@ import { observer } from "mobx-react"; import { ColorState, SketchPicker } from "react-color"; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../../fields/Doc"; -import { documentSchema } from "../../../../fields/documentSchemas"; import { InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { PrefetchProxy } from "../../../../fields/Proxy"; -import { listSpec, makeInterface } from "../../../../fields/Schema"; +import { listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnFalse, returnOne, returnTrue } from '../../../../Utils'; @@ -18,7 +17,7 @@ import { Docs } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { DocumentManager } from "../../../util/DocumentManager"; -import { Scripting } from "../../../util/Scripting"; +import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; import { SelectionManager } from "../../../util/SelectionManager"; import { undoBatch, UndoManager } from "../../../util/UndoManager"; import { CollectionDockingView } from "../../collections/CollectionDockingView"; @@ -39,11 +38,8 @@ export class PinProps { hidePresBox?: boolean; } -type PresBoxSchema = makeInterface<[typeof documentSchema]>; -const PresBoxDocument = makeInterface(documentSchema); - @observer -export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>(PresBoxDocument) { +export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } /** @@ -2461,7 +2457,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div>; } } -Scripting.addGlobal(function lookupPresBoxField(container: Doc, field: string, data: Doc) { +ScriptingGlobals.add(function lookupPresBoxField(container: Doc, field: string, data: Doc) { if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data); if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 35 : 31; if (field === 'presStatus') return container.presStatus; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 238d025dc..a4ec559f5 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -3,11 +3,10 @@ import { Tooltip } from "@material-ui/core"; import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { DataSym, Doc, Opt } from "../../../../fields/Doc"; -import { documentSchema } from '../../../../fields/documentSchemas'; import { Id } from "../../../../fields/FieldSymbols"; -import { createSchema, makeInterface } from '../../../../fields/Schema'; import { Cast, NumCast, StrCast } from "../../../../fields/Types"; -import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, emptyPath, returnEmptyDoclist } from "../../../../Utils"; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils"; +import { DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -16,36 +15,20 @@ import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from "../../EditableView"; +import { Colors } from "../../global/globalEnums"; import { DocumentView, DocumentViewProps } from "../../nodes/DocumentView"; import { FieldView, FieldViewProps } from '../../nodes/FieldView'; -import { PresBox } from "./PresBox"; -import { Colors } from "../../global/globalEnums"; import { StyleProp } from "../../StyleProvider"; +import { PresBox } from "./PresBox"; import "./PresElementBox.scss"; -import React = require("react"); -import { DocUtils } from "../../../documents/Documents"; import { PresMovement } from "./PresEnums"; - -export const presSchema = createSchema({ - presentationTargetDoc: Doc, - presBox: Doc, - presZoomButton: "boolean", - presNavButton: "boolean", - presHideTillShownButton: "boolean", - presFadeButton: "boolean", - presHideAfterButton: "boolean", - presGroupButton: "boolean", - presExpandInlineButton: "boolean" -}); - -type PresDocument = makeInterface<[typeof presSchema, typeof documentSchema]>; -const PresDocument = makeInterface(presSchema, documentSchema); +import React = require("react"); /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. */ @observer -export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDocument>(PresDocument) { +export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } _heightDisposer: IReactionDisposer | undefined; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 3f7f38bdf..1856c5353 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -26,7 +26,6 @@ import { StyleProp } from "../StyleProvider"; import { AnchorMenu } from "./AnchorMenu"; import { Annotation } from "./Annotation"; import "./PDFViewer.scss"; -const pdfjs = require('pdfjs-dist/es5/build/pdf.js'); import React = require("react"); const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); @@ -34,7 +33,7 @@ const _global = (window /* browser */ || global /* node */) as any; //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; // The workerSrc property shall be specified. -pdfjsLib.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@2.4.456/build/pdf.worker.min.js"; +pdfjsLib.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@2.13.216/build/pdf.worker.js"; interface IViewerProps extends FieldViewProps { Document: Doc; @@ -159,7 +158,7 @@ export class PDFViewer extends React.Component<IViewerProps> { initialLoad = async () => { if (this._pageSizes.length === 0) { this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages); - await Promise.all(this._pageSizes.map<Pdfjs.PDFPromise<any>>((val, i) => + await Promise.all(this._pageSizes.map((val, i) => this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => { const page0or180 = page.rotate === 0 || page.rotate === 180; this._pageSizes.splice(i, 1, { @@ -219,7 +218,7 @@ export class PDFViewer extends React.Component<IViewerProps> { } pagesinit = () => { - if (this._pdfViewer._setDocumentViewerElement.offsetParent) { + if (this._pdfViewer._setDocumentViewerElement?.offsetParent) { runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1); this.gotoPage(NumCast(this.props.Document._curPage, 1)); } @@ -555,7 +554,7 @@ export class PDFViewer extends React.Component<IViewerProps> { </div>; } @computed get pdfViewerDiv() { - return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && (this.props.isSelected() || this.props.isContentActive()) ? "-selected" : "")} ref={this._viewer} />; + return <div className={"pdfViewerDash-text" + (this.props.pointerEvents !== "none" && this._textSelecting && this.props.isContentActive() ? "-selected" : "")} ref={this._viewer} />; } @computed get contentScaling() { return this.props.ContentScaling?.() || 1; } @computed get standinViews() { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 09cfb2077..5fe2a5ab1 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,28 +1,18 @@ +import { Tooltip } from "@material-ui/core"; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc'; -import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from '../../../fields/FieldSymbols'; -import { createSchema, makeInterface } from '../../../fields/Schema'; import { StrCast } from '../../../fields/Types'; +import { DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; +import { DocumentManager } from '../../util/DocumentManager'; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { ViewBoxBaseComponent } from "../DocComponent"; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./SearchBox.scss"; -import { DocumentManager } from '../../util/DocumentManager'; -import { DocUtils } from '../../documents/Documents'; -import { Tooltip } from "@material-ui/core"; -import { DictationOverlay } from '../DictationOverlay'; -import { CollectionSchemaBooleanCell } from '../collections/collectionSchema/CollectionSchemaCells'; - -export const searchSchema = createSchema({ - Document: Doc -}); -type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>; -const SearchBoxDocument = makeInterface(documentSchema, searchSchema); const DAMPENING_FACTOR = 0.9; const MAX_ITERATIONS = 25; @@ -38,7 +28,7 @@ export interface SearchBoxProps extends FieldViewProps { * the search panel on the left side of the screen. */ @observer -export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDocument>(SearchBoxDocument) { +export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); } public static Instance: SearchBox; diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx index 505a8da7e..e0d328c89 100644 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ b/src/client/views/webcam/DashWebRTCVideo.tsx @@ -12,6 +12,7 @@ import "./DashWebRTCVideo.scss"; import { hangup, initialize, refreshVideos } from "./WebCamLogic"; import React = require("react"); import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { IconLookup } from "@fortawesome/fontawesome-svg-core"; /** @@ -64,8 +65,8 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV }}></video> </div> <div className="buttonContainer"> - <div className="videoButtons" style={{ background: "red" }} onClick={this.onClickHangUp}><FontAwesomeIcon icon={faPhoneSlash} color="white" /></div> - <div className="videoButtons" style={{ background: "green" }} onClick={this.onClickRefresh}><FontAwesomeIcon icon={faSync} color="white" /></div> + <div className="videoButtons" style={{ background: "red" }} onClick={this.onClickHangUp}><FontAwesomeIcon icon={faPhoneSlash as IconLookup} color="white" /></div> + <div className="videoButtons" style={{ background: "green" }} onClick={this.onClickRefresh}><FontAwesomeIcon icon={faSync as IconLookup} color="white" /></div> </div> </div >; |
