aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/DocumentButtonBar.tsx2
-rw-r--r--src/client/views/DocumentDecorations.tsx8
-rw-r--r--src/client/views/Main.tsx6
-rw-r--r--src/client/views/MainView.tsx113
-rw-r--r--src/client/views/OverlayView.tsx48
-rw-r--r--src/client/views/TemplateMenu.tsx16
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx5
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx3
-rw-r--r--src/client/views/collections/CollectionMapView.tsx4
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx14
-rw-r--r--src/client/views/collections/CollectionPileView.scss3
-rw-r--r--src/client/views/collections/CollectionPileView.tsx59
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx4
-rw-r--r--src/client/views/collections/CollectionSubView.tsx37
-rw-r--r--src/client/views/collections/CollectionView.tsx9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss27
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx180
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx113
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx78
-rw-r--r--src/client/views/nodes/ComparisonBox.scss87
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx109
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.scss1
-rw-r--r--src/client/views/nodes/DocumentView.tsx39
-rw-r--r--src/client/views/nodes/ImageBox.tsx7
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx4
-rw-r--r--src/client/views/nodes/LabelBox.tsx5
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx2
-rw-r--r--src/client/views/nodes/PresBox.tsx30
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx13
-rw-r--r--src/client/views/nodes/VideoBox.tsx66
-rw-r--r--src/client/views/nodes/WebBox.tsx203
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx11
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts18
-rw-r--r--src/client/views/pdf/PDFViewer.tsx1
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx26
-rw-r--r--src/client/views/search/CheckBox.tsx1
-rw-r--r--src/client/views/webcam/WebCamLogic.js5
39 files changed, 924 insertions, 440 deletions
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 2db5cd3ba..a35a8869c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -121,7 +121,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
dragComplete: dropEv => {
const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
if (this.view0 && linkDoc) {
- Doc.GetProto(linkDoc).linkRelationship = "hyperlink";
+ !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
// we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
// the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index fc7c16296..04f02c683 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -21,6 +21,7 @@ import { Id } from '../../fields/FieldSymbols';
import e = require('express');
import { CollectionDockingView } from './collections/CollectionDockingView';
import { SnappingManager } from '../util/SnappingManager';
+import { HtmlField } from '../../fields/HtmlField';
library.add(faCaretUp);
library.add(faObjectGroup);
@@ -289,7 +290,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let dX = 0, dY = 0, dW = 0, dH = 0;
const unfreeze = () =>
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) =>
- (element.rootDoc.type === DocumentType.RTF && element.layoutDoc._nativeHeight) && element.toggleNativeDimensions()));
+ ((element.rootDoc.type === DocumentType.RTF ||
+ element.rootDoc.type === DocumentType.COMPARISON ||
+ (element.rootDoc.type === DocumentType.WEB && Doc.LayoutField(element.rootDoc) instanceof HtmlField))
+ && element.layoutDoc._nativeHeight) && element.toggleNativeDimensions()));
switch (this._resizeHdlId) {
case "": break;
case "documentDecorations-topLeftResizer":
@@ -483,7 +487,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
<span style={{ width: "calc(100% - 25px)", display: "inline-block" }}>{`${this.selectionTitle}`}</span>
</div>
- </>
+ </>;
bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2;
bounds.y = Math.max(0, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 17c001971..6878658a8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -5,12 +5,16 @@ import * as ReactDOM from 'react-dom';
import * as React from 'react';
import { DocServer } from "../DocServer";
import { AssignAllExtensions } from "../../extensions/General/Extensions";
+import { Networking } from "../Network";
AssignAllExtensions();
+export let resolvedPorts: { server: number, socket: number };
+
(async () => {
const info = await CurrentUserUtils.loadCurrentUser();
- DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email);
+ resolvedPorts = JSON.parse(await Networking.FetchFromServer("/resolvedPorts"));
+ DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, info.email);
await Docs.Prototypes.initialize();
if (info.id !== "__guest__") {
// a guest will not have an id registered
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 978cf7868..358de2333 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,11 +1,11 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import {
- faTrashAlt, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -50,7 +50,6 @@ import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
import { ScriptField } from '../../fields/ScriptField';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
-import { DragManager } from '../util/DragManager';
import { SnappingManager } from '../util/SnappingManager';
@observer
@@ -66,7 +65,7 @@ export class MainView extends React.Component {
@observable private _panelHeight: number = 0;
@observable private _flyoutTranslate: boolean = true;
@observable public flyoutWidth: number = 250;
- private get darkScheme() { return BoolCast(Cast(this.userDoc.activeWorkspace, Doc, null)?.darkScheme); }
+ private get darkScheme() { return BoolCast(Cast(this.userDoc?.activeWorkspace, Doc, null)?.darkScheme); }
@computed private get userDoc() { return Doc.UserDoc(); }
@computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; }
@@ -114,77 +113,12 @@ export class MainView extends React.Component {
}
}
- library.add(faTrashAlt);
- library.add(faAngleRight);
- library.add(faBell);
- library.add(faTrash);
- library.add(faCamera);
- library.add(faExpand);
- library.add(faCaretDown);
- library.add(faCaretRight);
- library.add(faCaretSquareDown);
- library.add(faCaretSquareRight);
- library.add(faArrowsAltH);
- library.add(faPlus, faMinus);
- library.add(faTerminal);
- library.add(faToggleOn);
- library.add(faLocationArrow);
- library.add(faSearch);
- library.add(fileSolid);
- library.add(faFileDownload);
- library.add(faStop);
- library.add(faCalculator);
- library.add(faWindowMaximize);
- library.add(faFileAlt);
- library.add(faAddressCard);
- library.add(faQuestionCircle);
- library.add(faStickyNote);
- library.add(faFont);
- library.add(faExclamation);
- library.add(faPortrait);
- library.add(faCat);
- library.add(faFilePdf);
- library.add(faObjectGroup);
- library.add(faTv);
- library.add(faGlobeAsia);
- library.add(faUndoAlt);
- library.add(faRedoAlt);
- library.add(faMousePointer);
- library.add(faPen);
- library.add(faHighlighter);
- library.add(faEraser);
- library.add(faFileAudio);
- library.add(faPenNib);
- library.add(faMicrophone);
- library.add(faFilm);
- library.add(faMusic);
- library.add(faTree);
- library.add(faPlay);
- library.add(faCompressArrowsAlt);
- library.add(faPause);
- library.add(faClone);
- library.add(faCut);
- library.add(faCommentAlt);
- library.add(faThumbtack);
- library.add(faLongArrowAltRight);
- library.add(faCheck);
- library.add(faCaretUp);
- library.add(faFilter);
- library.add(faBullseye);
- library.add(faArrowLeft);
- library.add(faArrowRight);
- library.add(faArrowDown);
- library.add(faArrowUp);
- library.add(faCloudUploadAlt);
- library.add(faBolt);
- library.add(faVideo);
- library.add(faChevronRight);
- library.add(faEllipsisV);
- library.add(faMusic);
- library.add(faPhone);
- library.add(faClipboard);
- library.add(faStamp);
- library.add(faExternalLinkAlt);
+ library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, fileSolid,
+ faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
+ faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
+ faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
+ faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -204,8 +138,8 @@ export class MainView extends React.Component {
globalPointerUp = () => this.isPointerDown = false;
initEventListeners = () => {
- window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
- window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
+ window.addEventListener("drop", (e) => { e.preventDefault(); }, false); // drop event handler
+ window.addEventListener("dragover", (e) => { e.preventDefault(); }, false); // drag event handler
// click interactions for the context menu
document.addEventListener("pointerdown", this.globalPointerDown);
document.addEventListener("pointerup", this.globalPointerUp);
@@ -252,15 +186,17 @@ export class MainView extends React.Component {
_LODdisable: true
};
const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
- const mainDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+ const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
- mainDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!]);
- mainDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors"]);
+ const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+ const cloneWorkspace = ScriptField.MakeScript(`cloneWorkspace()`);
+ workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneWorkspace!]);
+ workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Workspace Layout"]);
- Doc.AddDocToList(workspaces, "data", mainDoc);
+ Doc.AddDocToList(workspaces, "data", workspaceDoc);
// bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
- setTimeout(() => this.openWorkspace(mainDoc), 0);
+ setTimeout(() => this.openWorkspace(workspaceDoc), 0);
}
@action
@@ -307,7 +243,6 @@ export class MainView extends React.Component {
onDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
- console.log("Drop");
}
@action
@@ -374,7 +309,9 @@ export class MainView extends React.Component {
const width = this.flyoutWidth;
return <Measure offset onResize={this.onResize}>
{({ measureRef }) =>
- <div ref={measureRef} className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}>
+ <div ref={measureRef} className="mainContent-div" onDragEnter={e => {
+ console.log("ENTERING");
+ }} onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}>
{!mainContainer ? (null) : this.mainDocView}
</div>
}
@@ -628,3 +565,11 @@ export class MainView extends React.Component {
}
}
Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
+Scripting.addGlobal(function toggleComicMode() { Doc.UserDoc().fontFamily = "Comic Sans MS"; Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; });
+Scripting.addGlobal(function cloneWorkspace() {
+ const copiedWorkspace = Doc.MakeCopy(Cast(Doc.UserDoc().activeWorkspace, Doc, null), true);
+ const workspaces = Cast(Doc.UserDoc().myWorkspaces, Doc, null);
+ Doc.AddDocToList(workspaces, "data", copiedWorkspace);
+ // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+ setTimeout(() => MainView.Instance.openWorkspace(copiedWorkspace), 0);
+});
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index bfa44fe47..cfa869fb2 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -3,14 +3,17 @@ import { observer } from "mobx-react";
import * as React from "react";
import { Doc, DocListCast, Opt } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
-import { NumCast } from "../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils } from "../../Utils";
+import { NumCast, Cast } from "../../fields/Types";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, Utils, setupMoveUpEvents } from "../../Utils";
import { Transform } from "../util/Transform";
import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView";
import { DocumentView } from "./nodes/DocumentView";
import './OverlayView.scss';
import { Scripting } from "../util/Scripting";
import { ScriptingRepl } from './ScriptingRepl';
+import { DragManager } from "../util/DragManager";
+import { listSpec } from "../../fields/Schema";
+import { List } from "../../fields/List";
export type OverlayDisposer = () => void;
@@ -139,46 +142,51 @@ export class OverlayView extends React.Component {
return remove;
}
+
@computed get overlayDocs() {
const userDocOverlays = Doc.UserDoc().myOverlayDocuments;
if (!userDocOverlays) {
- return (null);
+ return null;
}
return userDocOverlays instanceof Doc && DocListCast(userDocOverlays.data).map(d => {
setTimeout(() => d.inOverlay = true, 0);
let offsetx = 0, offsety = 0;
- const onPointerMove = action((e: PointerEvent) => {
+ const dref = React.createRef<HTMLDivElement>();
+ const onPointerMove = action((e: PointerEvent, down: number[]) => {
if (e.buttons === 1) {
d.x = e.clientX + offsetx;
d.y = e.clientY + offsety;
- e.stopPropagation();
- e.preventDefault();
}
- });
- const onPointerUp = action((e: PointerEvent) => {
- document.removeEventListener("pointermove", onPointerMove);
- document.removeEventListener("pointerup", onPointerUp);
- e.stopPropagation();
- e.preventDefault();
+ if (e.metaKey) {
+ const dragData = new DragManager.DocumentDragData([d]);
+ d.removeDropProperties = new List<string>(["inOverlay"]);
+ dragData.offset = [-offsetx, -offsety];
+ dragData.dropAction = "move";
+ dragData.removeDocument = (doc: Doc | Doc[]) => {
+ const docs = (doc instanceof Doc) ? [doc] : doc;
+ docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null), "data", d));
+ return true;
+ };
+ dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
+ return dragData.removeDocument!(doc) ? addDocument(doc) : false;
+ };
+ DragManager.StartDocumentDrag([dref.current!], dragData, down[0], down[1]);
+ return true;
+ }
+ return false;
});
const onPointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, onPointerMove, emptyFunction, emptyFunction);
offsetx = NumCast(d.x) - e.clientX;
offsety = NumCast(d.y) - e.clientY;
- e.stopPropagation();
- e.preventDefault();
- document.addEventListener("pointermove", onPointerMove);
- document.addEventListener("pointerup", onPointerUp);
};
- return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)` }}>
+ return <div className="overlayView-doc" ref={dref} key={d[Id]} onPointerDown={onPointerDown} style={{ width: NumCast(d._width), height: NumCast(d._height), transform: `translate(${d.x}px, ${d.y}px)` }}>
<DocumentView
Document={d}
LibraryPath={emptyPath}
ChromeHeight={returnZero}
rootSelected={returnTrue}
- // isSelected={returnFalse}
- // select={emptyFunction}
- // layoutKey={"layout"}
bringToFront={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index f5e95e4fd..77e6ebf44 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -15,6 +15,7 @@ import { Transform } from "../util/Transform";
import { ScriptField, ComputedField } from "../../fields/ScriptField";
import { Scripting } from "../util/Scripting";
import { List } from "../../fields/List";
+import { TraceMobx } from "../../fields/util";
@observer
class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
@@ -110,7 +111,12 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
return ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
{ docs: new List<Doc>(this.props.docViews.map(dv => dv.props.Document)) });
}
+ templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
+ const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
+ return StrCast(selDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
+ }
render() {
+ TraceMobx();
const firstDoc = this.props.docViews[0].props.Document;
const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data);
@@ -123,7 +129,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={firstDoc.z ? true : false} toggle={this.toggleFloat} />);
templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
- addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction(`templateIsUsed(self,firstDoc)`, {}, { firstDoc }));
+ addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template));
this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
<OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
return <ul className="template-list" style={{ display: "block" }}>
@@ -172,11 +178,3 @@ Scripting.addGlobal(function switchView(doc: Doc, template: Doc | undefined) {
const templateTitle = StrCast(template?.title);
return templateTitle && Doc.makeCustomViewClicked(doc, Docs.Create.FreeformDocument, templateTitle, template);
});
-
-Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, selDoc: Doc) {
- if (selDoc) {
- const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
- return StrCast(selDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
- }
- return false;
-}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 39bb9bc23..f65a89422 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -9,7 +9,6 @@ import { DragManager } from '../../util/DragManager';
import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
import "./CollectionCarouselView.scss";
import { CollectionSubView } from './CollectionSubView';
-import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
import { Doc } from '../../../fields/Doc';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { ContextMenu } from '../ContextMenu';
@@ -76,10 +75,10 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
@computed get buttons() {
return <>
<div key="back" className="carouselView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={this.goback}>
- <FontAwesomeIcon icon={faCaretLeft} size={"2x"} />
+ <FontAwesomeIcon icon={"caret-left"} size={"2x"} />
</div>
<div key="fwd" className="carouselView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={this.advance}>
- <FontAwesomeIcon icon={faCaretRight} size={"2x"} />
+ <FontAwesomeIcon icon={"caret-right"} size={"2x"} />
</div>
</>;
}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 745476ef7..6f5a3dfe4 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,5 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faFile } from '@fortawesome/free-solid-svg-icons';
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
import { action, computed, Lambda, observable, reaction, runInAction, trace } from "mobx";
@@ -31,7 +29,6 @@ import { DockingViewButtonSelector } from './ParentDocumentSelector';
import React = require("react");
import { CollectionViewType } from './CollectionView';
import { SnappingManager } from '../../util/SnappingManager';
-library.add(faFile);
const _global = (window /* browser */ || global /* node */) as any;
@observer
diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx
index d91337ce9..a0b7cd8a8 100644
--- a/src/client/views/collections/CollectionMapView.tsx
+++ b/src/client/views/collections/CollectionMapView.tsx
@@ -226,7 +226,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
initialCenter={center}
center={center}
onIdle={(_props?: IMapProps, map?: google.maps.Map) => {
- if (this.layoutDoc.lockedTransform) {
+ if (this.layoutDoc._lockedTransform) {
// reset zoom (ideally, we could probably can tell the map to disallow zooming somehow instead)
map?.setZoom(center?.zoom || 10);
map?.setCenter({ lat: center?.lat!, lng: center?.lng! });
@@ -238,7 +238,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps>
}
}}
onDragend={(_props?: IMapProps, map?: google.maps.Map) => {
- if (this.layoutDoc.lockedTransform) {
+ if (this.layoutDoc._lockedTransform) {
// reset the drag (ideally, we could probably can tell the map to disallow dragging somehow instead)
map?.setCenter({ lat: center?.lat!, lng: center?.lng! });
} else {
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index d6cb174cc..cc7a9f5ac 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -1,6 +1,4 @@
import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
@@ -22,8 +20,6 @@ const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
-library.add(faPalette);
-
interface CMVFieldRowProps {
rows: () => number;
headings: () => object[];
@@ -244,13 +240,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
toggle: this.toggleVisibility,
color: this.color
};
+ const showChrome = (chromeStatus !== 'view-mode' && chromeStatus !== 'disabled');
+ const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
return this.collapsed ? (null) :
<div style={{ position: "relative" }}>
- {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
+ {showChrome ?
<div className="collectionStackingView-addDocumentButton"
style={{
- width: style.columnWidth / style.numGroupColumns,
- padding: NumCast(this.props.parent.layoutDoc._yPadding)
+ //width: style.columnWidth / style.numGroupColumns,
+ padding: `${NumCast(this.props.parent.layoutDoc._yPadding, this.props.parent.yMargin)}px 0px 0px 0px`
}}>
<EditableView {...newEditableViewProps} />
</div> : null
@@ -258,7 +256,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
<div className={`collectionStackingView-masonryGrid`}
ref={this._contRef}
style={{
- padding: `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px`,
+ padding: stackPad,
width: this.props.parent.NodeWidth,
gridGap: this.props.parent.gridGap,
gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ""),
diff --git a/src/client/views/collections/CollectionPileView.scss b/src/client/views/collections/CollectionPileView.scss
index ac874b663..48d07e42b 100644
--- a/src/client/views/collections/CollectionPileView.scss
+++ b/src/client/views/collections/CollectionPileView.scss
@@ -5,4 +5,7 @@
height: 100%;
width: 100%;
overflow: visible;
+ .collectionPileView-innards {
+ width:100%;
+ }
}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index e3bcf2a21..020a87b2e 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,18 +1,17 @@
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { HeightSym, Opt, WidthSym, Doc } from "../../../fields/Doc";
import { ScriptField } from "../../../fields/ScriptField";
import { BoolCast, NumCast, StrCast } from "../../../fields/Types";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionPileView.scss";
import React = require("react");
import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
import { SelectionManager } from "../../util/SelectionManager";
-import { UndoManager } from "../../util/UndoManager";
+import { UndoManager, undoBatch } from "../../util/UndoManager";
import { SnappingManager } from "../../util/SnappingManager";
+import { DragManager } from "../../util/DragManager";
@observer
export class CollectionPileView extends CollectionSubView(doc => doc) {
@@ -20,10 +19,12 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
_doubleTap: boolean | undefined = false;
_originalChrome: string = "";
@observable _contentsActive = true;
- @observable _layoutEngine = "pass";
@observable _collapsed: boolean = false;
@observable _childClickedScript: Opt<ScriptField>;
componentDidMount() {
+ if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") {
+ this.Document._layoutEngine = "pass";
+ }
this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
this.layoutDoc._chromeStatus = "disabled";
this.layoutDoc.hideFilterView = true;
@@ -33,49 +34,54 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
this.layoutDoc._chromeStatus = this._originalChrome;
}
- layoutEngine = () => this._layoutEngine;
+ layoutEngine = () => StrCast(this.Document._layoutEngine);
@computed get contents() {
- return <div className="collectionPileView-innards" style={{
- width: "100%",
- pointerEvents: this.layoutEngine() !== "pass" && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none"
- }} >
+ return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" ? undefined : "none" }} >
<CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
</div>;
}
-
- specificMenu = (e: React.MouseEvent) => {
- const layoutItems: ContextMenuProps[] = [];
- const doc = this.props.Document;
-
- ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
- }
-
toggleStarburst = action(() => {
- if (this._layoutEngine === 'starburst') {
+ if (this.layoutEngine() === 'starburst') {
const defaultSize = 110;
this.layoutDoc._overflow = undefined;
+ this.childDocs.forEach(d => Doc.iconify(d));
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
- this._layoutEngine = 'pass';
+ Doc.pileup(this.childDocs);
+ this.layoutDoc._panX = 0;
+ this.layoutDoc._panY = -10;
+ this.props.Document._layoutEngine = 'pass';
} else {
const defaultSize = 25;
this.layoutDoc._overflow = 'visible';
!this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
!this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
- if (this._layoutEngine === 'pass') {
+ if (this.layoutEngine() === 'pass') {
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
}
+ this.layoutDoc._panX = this.layoutDoc._panY = 0;
this.layoutDoc._width = this.layoutDoc._height = defaultSize;
- this._layoutEngine = 'starburst';
+ this.props.Document._layoutEngine = 'starburst';
}
});
+ @undoBatch
+ @action
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ if (super.onInternalDrop(e, de)) {
+ if (de.complete.docDragData) {
+ Doc.pileup(this.childDocs);
+ }
+ }
+ return true;
+ }
+
_undoBatch: UndoManager.Batch | undefined;
pointerDown = (e: React.PointerEvent) => {
let dist = 0;
@@ -107,20 +113,17 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
}
onClick = (e: React.MouseEvent) => {
- if (e.button === 0 && (this._doubleTap || this.layoutEngine() === "starburst")) {
+ if (e.button === 0 && this._doubleTap) {
SelectionManager.DeselectAll();
this.toggleStarburst();
e.stopPropagation();
}
- // else if (this.layoutEngine() === "pass") {
- // runInAction(() => this._contentsActive = false);
- // setTimeout(action(() => this._contentsActive = true), 300);
- // }
}
render() {
- return <div className={"collectionPileView"} onContextMenu={this.specificMenu} onClick={this.onClick} onPointerDown={this.pointerDown}
+ return <div className={"collectionPileView"} onClick={this.onClick} onPointerDown={this.pointerDown}
+ ref={this.createDashEventsTarget}
style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
{this.contents}
</div>;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index cc6077d98..6949670d6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -26,6 +26,7 @@ import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewField
import { CollectionSubView } from "./CollectionSubView";
import { CollectionViewType } from "./CollectionView";
import { SnappingManager } from "../../util/SnappingManager";
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
const _global = (window /* browser */ || global /* node */) as any;
type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
@@ -191,8 +192,8 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
}
getDisplayDoc(doc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
- const layoutDoc = Doc.Layout(doc, this.props.ChildLayoutTemplate?.());
const height = () => this.getDocHeight(doc);
+ const opacity = () => this.Document.currentTimecode === undefined ? this.props.childOpacity?.() : CollectionFreeFormDocumentView.getValues(doc, this.Document.currentTimecode || 0)?.opacity;
return <ContentFittingDocumentView
Document={doc}
DataDoc={dataDoc || (doc[DataSym] !== doc && doc[DataSym])}
@@ -213,6 +214,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
ScreenToLocalTransform={dxf}
+ opacity={opacity}
focus={this.focusDocument}
ContainingCollectionDoc={this.props.CollectionView?.props.Document}
ContainingCollectionView={this.props.CollectionView}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index c9eb08b45..423eb1d90 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -6,14 +6,14 @@ import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
-import { Cast, ScriptCast } from "../../../fields/Types";
+import { Cast, ScriptCast, NumCast } from "../../../fields/Types";
import { GestureUtils } from "../../../pen-gestures/GestureUtils";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { Upload } from "../../../server/SharedMediaTypes";
import { Utils } from "../../../Utils";
import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions } from "../../documents/Documents";
+import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { Networking } from "../../Network";
import { DragManager, dropActionType } from "../../util/DragManager";
@@ -25,6 +25,8 @@ import { FieldViewProps } from "../nodes/FieldView";
import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
import { CollectionView } from "./CollectionView";
import React = require("react");
+import { SelectionManager } from "../../util/SelectionManager";
+import { WebField } from "../../../fields/URLField";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc | Doc[]) => boolean;
@@ -44,6 +46,7 @@ export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: Opt<CollectionView>;
children?: never | (() => JSX.Element[]) | React.ReactNode;
ChildLayoutTemplate?: () => Doc;
+ childOpacity?: () => number;
ChildLayoutString?: string;
childClickScript?: ScriptField;
childDoubleClickScript?: ScriptField;
@@ -215,7 +218,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData });
if (docDragData) {
let added = false;
- if (docDragData.dropAction || docDragData.userDropAction) {
+ const dropaction = docDragData.dropAction || docDragData.userDropAction;
+ if (dropaction && dropaction !== "move") {
added = this.addDocument(docDragData.droppedDocuments);
} else if (docDragData.moveDocument) {
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
@@ -323,9 +327,30 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
});
} else {
- const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
+ let srcUrl: string | undefined;
+ let srcWeb: Doc | undefined;
+ if (SelectionManager.SelectedDocuments().length) {
+ srcWeb = SelectionManager.SelectedDocuments()[0].props.Document;
+ srcUrl = (srcWeb.data as WebField).url.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
+ }
+ const reg = new RegExp(Utils.prepend(""), "g");
+ const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
+ const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
Doc.GetProto(htmlDoc)["data-text"] = text;
this.props.addDocument(htmlDoc);
+ if (srcWeb) {
+ const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any);
+ if (focusNode) {
+ const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
+ const x = (rect?.x || 0);
+ const y = NumCast(srcWeb.scrollTop) + (rect?.y || 0);
+ const anchor = Docs.Create.FreeformDocument([], { _LODdisable: true, _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
+ anchor.context = srcWeb;
+ const key = Doc.LayoutFieldKey(srcWeb);
+ Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
+ DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor });
+ }
+ }
}
return;
}
@@ -334,7 +359,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (text) {
if (text.includes("www.youtube.com/watch")) {
- const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+ const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/").split("&")[0];
addDocument(Docs.Create.VideoDocument(url, {
...options,
title: url,
@@ -427,7 +452,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
if (generatedDocuments.length) {
const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d));
if (set) {
- addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!));
+ addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!)!);
} else {
generatedDocuments.forEach(addDocument);
}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 311e8d3a1..d1fbb445d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -74,6 +74,7 @@ export interface CollectionViewCustomProps {
filterAddDocument: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
childLayoutTemplate?: () => Opt<Doc>; // specify a layout Doc template to use for children of the collection
childLayoutString?: string; // specify a layout string to use for children of the collection
+ childOpacity?: () => number;
}
export interface CollectionRenderProps {
@@ -119,10 +120,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@action.bound
addDocument = (doc: Doc | Doc[]): boolean => {
- if (doc instanceof Doc) {
- if (this.props.filterAddDocument?.(doc) === false) {
- return false;
- }
+ if (this.props.filterAddDocument?.(doc) === false) {
+ return false;
}
const docs = doc instanceof Doc ? [doc] : doc;
const targetDataDoc = this.props.Document[DataSym];
@@ -529,7 +528,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return (<div className={"collectionView"}
style={{
pointerEvents: this.props.Document.isBackground ? "none" : undefined,
- boxShadow: this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
+ boxShadow: Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
`${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31)" : "#9c9396"} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
}}
onContextMenu={this.onContextMenu}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 3860ce2d7..a4fd5384f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -25,6 +25,7 @@ export interface ViewDefBounds {
fontSize?: number;
highlight?: boolean;
color?: string;
+ opacity?: number;
replica?: string;
pair?: { layout: Doc, data?: Doc };
}
@@ -37,6 +38,7 @@ export interface PoolData {
width?: number;
height?: number;
color?: string;
+ opacity?: number;
transition?: string;
highlight?: boolean;
replica: string;
@@ -416,7 +418,7 @@ function normalizeResults(
height: newPosRaw.height! * scale,
pair: ele[1].pair
};
- poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos });
+ poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "all 1s", ...newPos });
}
});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 60c39c825..5478a1c4a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -41,6 +41,33 @@
// touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
touch-action: none;
+ .fwdKeyframe, .numKeyframe, .backKeyframe{
+ cursor: pointer;
+ position: absolute;
+ width: 20;
+ height:20;
+ bottom:0;
+ background:gray;
+ }
+ .backKeyframe {
+ right:45;
+ svg {
+ display:block;
+ margin:auto;
+ }
+ }
+ .numKeyframe {
+ right:25;
+ text-align:center;
+ }
+ .fwdKeyframe {
+ right:5;
+ svg {
+ display:block;
+ margin:auto;
+ }
+ }
+
.collectionfreeformview-placeholder {
background: gray;
width: 100%;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bf679309c..4840bb7e7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -18,7 +18,7 @@ import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
-import { Docs } from "../../../documents/Documents";
+import { Docs, DocUtils } from "../../../documents/Documents";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager, dropActionType } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
@@ -46,6 +46,7 @@ import React = require("react");
import { CollectionViewType } from "../CollectionView";
import { Timeline } from "../../animationtimeline/Timeline";
import { SnappingManager } from "../../../util/SnappingManager";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -53,6 +54,9 @@ export const panZoomSchema = createSchema({
_panX: "number",
_panY: "number",
scale: "number",
+ currentTimecode: "number",
+ displayTimecode: "number",
+ currentFrame: "number",
arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
@@ -123,28 +127,64 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
this.addDocument(newBox);
}
- addDocument = (newBox: Doc | Doc[]) => {
- const timecode = Cast(this.props.Document.timecode, "number", null);
- if (timecode !== undefined) {
- ((newBox instanceof Doc) ? [newBox] : newBox).map(doc => {
- doc["x-indexed"] = new List<number>(numberRange(timecode + 1).map(i => NumCast(doc.x)));
- doc["y-indexed"] = new List<number>(numberRange(timecode + 1).map(i => NumCast(doc.y)));
- doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document });
- doc.x = ComputedField.MakeInterpolated("x", "timecode");
- doc.y = ComputedField.MakeInterpolated("y", "timecode");
- });
- }
-
+ addDocument = action((newBox: Doc | Doc[]) => {
+ let retVal = false;
if (newBox instanceof Doc) {
- const added = this.props.addDocument(newBox);
- added && this.bringToFront(newBox);
- added && this.updateCluster(newBox);
- return added;
+ retVal = this.props.addDocument(newBox);
+ retVal && this.bringToFront(newBox);
+ retVal && this.updateCluster(newBox);
} else {
- return this.props.addDocument(newBox);
+ retVal = this.props.addDocument(newBox);
// bcz: deal with clusters
}
+ if (retVal) {
+ const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox;
+ for (let i = 0; i < newBoxes.length; i++) {
+ const newBox = newBoxes[i];
+ if (newBox.activeFrame !== undefined) {
+ const x = newBox.x;
+ const y = newBox.y;
+ delete newBox["x-indexed"];
+ delete newBox["y-indexed"];
+ delete newBox["opacity-indexed"];
+ delete newBox.x;
+ delete newBox.y;
+ delete newBox.activeFrame;
+ newBox.x = x;
+ newBox.y = y;
+ }
+ }
+ if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentFrame);
+ }
+ }
+ return retVal;
+ })
+
+ @undoBatch
+ @action
+ nextKeyframe = (): void => {
+ const currentFrame = this.Document.currentFrame;
+ if (currentFrame === undefined) {
+ this.Document.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
+ this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
+ }
+ @undoBatch
+ @action
+ prevKeyframe = (): void => {
+ const currentFrame = this.Document.currentFrame;
+ if (currentFrame === undefined) {
+ this.Document.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
+ }
+ CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
+ this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
}
+
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
@@ -170,6 +210,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const [xp, yp] = xf.transformPoint(de.x, de.y);
const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ if (!this.isAnnotationOverlay && de.complete.linkDragData && de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
+ const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" });
+ this.props.addDocument(source);
+ (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceDocument },
+ "doc annotation")); // TODODO this is where in text links get passed
+ e.stopPropagation();
+ return true;
+ }
if (super.onInternalDrop(e, de)) {
if (de.complete.docDragData) {
if (de.complete.docDragData.droppedDocuments.length) {
@@ -185,8 +233,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
for (let i = 0; i < droppedDocs.length; i++) {
const d = droppedDocs[i];
const layoutDoc = Doc.Layout(d);
- d.x = x + NumCast(d.x) - dropX;
- d.y = y + NumCast(d.y) - dropY;
+ if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
+ CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropX, y + vals.y - dropY, vals.opacity);
+ } else {
+ d.x = x + NumCast(d.x) - dropX;
+ d.y = y + NumCast(d.y) - dropY;
+ }
if (!NumCast(layoutDoc._width)) {
layoutDoc._width = 300;
}
@@ -744,7 +797,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return;
+ if (this.layoutDoc._lockedTransform || this.props.Document.inOverlay) return;
if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
e.stopPropagation();
}
@@ -780,7 +833,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
}
}
- if (!this.Document.lockedTransform || this.Document.inOverlay) {
+ if (!this.layoutDoc._lockedTransform || this.Document.inOverlay) {
this.Document.panTransformType = panType;
const scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -953,10 +1006,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
}
const layoutDoc = Doc.Layout(params.pair.layout);
- const { x, y, z, color, zIndex } = params.pair.layout;
+ const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
+ CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
+ const { z, color, zIndex } = params.pair.layout;
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
- transition: StrCast(layoutDoc.transition),
+ transition: StrCast(layoutDoc.transition), opacity: this.Document.editing ? 1 : Cast(opacity, "number", null),
width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
};
}
@@ -1016,8 +1071,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
doFreeformLayout(poolData: Map<string, PoolData>) {
const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
- const state = initResult && initResult.success ? initResult.result.scriptState : undefined;
- const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
+ const state = initResult?.success ? initResult.result.scriptState : undefined;
+ const elements = initResult?.success ? this.viewDefsToJSX(initResult.result.views) : [];
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state });
@@ -1048,7 +1103,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
for (const entry of array) {
const lastPos = this._cachedPool.get(entry[0]); // last computed pos
const newPos = entry[1];
- if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
+ if (!lastPos || newPos.opacity !== lastPos.opacity || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
this._layoutPoolData.set(entry[0], newPos);
}
if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
@@ -1072,7 +1127,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.backgroundActive ?
true :
(this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
- jitterRotation={NumCast(this.props.Document._jitterRotation)}
+ jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))}
//fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this
FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
@@ -1144,37 +1199,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@undoBatch
@action
- snaphsotInterpolated = (): void => {
- if (this.props.Document.timecode === undefined) {
- this.childDocs.map(doc => {
- this.props.Document.timecode = 0;
- doc["x-indexed"] = new List<number>([NumCast(doc.x)]);
- doc["y-indexed"] = new List<number>([NumCast(doc.y)]);
- doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document });
- doc.x = ComputedField.MakeInterpolated("x", "timecode");
- doc.y = ComputedField.MakeInterpolated("y", "timecode");
- });
- }
- const timecode = NumCast(this.props.Document.timecode);
- this.childDocs.map(doc => {
- const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
- const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
- xindexed.length <= timecode + 1 && xindexed.push(NumCast(doc.x));
- yindexed.length <= timecode + 1 && yindexed.push(NumCast(doc.y));
- });
- this.childDocs.map(doc => doc.transition = "transform 1s");
- this.props.Document.timecode = Math.max(0, timecode + 1);
- setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010);
- }
- @undoBatch
- @action
- backupInterpolated = (): void => {
- this.childDocs.map(doc => doc.transition = "transform 1s");
- this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1);
- setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010);
+ toggleLockTransform = (): void => {
+ this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true;
}
-
private thumbIdentifier?: number;
onContextMenu = (e: React.MouseEvent) => {
@@ -1185,8 +1213,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this._timelineVisible = !this._timelineVisible;
}), icon: this._timelineVisible ? faEyeSlash : faEye
});
- ContextMenu.Instance.addItem({ description: "Advance", event: this.snaphsotInterpolated, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
- ContextMenu.Instance.addItem({ description: "Backup ", event: this.backupInterpolated, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
@@ -1197,6 +1223,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
+ optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
// layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
optionItems.push({
@@ -1342,18 +1369,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
- // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
- // this.Document.fitX = this.contentBounds && this.contentBounds.x;
- // this.Document.fitY = this.contentBounds && this.contentBounds.y;
- // this.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
- // this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
- // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
- // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
- return <div className={"collectionfreeformview-container"}
- ref={this.createDashEventsTarget}
+ return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
onPointerOver={this.onPointerOver}
- onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: DraggingManager.GetIsDragging() ? "all" : undefined,
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
+ onWheel={this.onPointerWheel}
+ onClick={this.onClick}
+ onPointerDown={this.onPointerDown}
+ onPointerMove={this.onCursorMove}
+ onDrop={this.onExternalDrop.bind(this)}
+ onDragOver={e => {
+ e.preventDefault();
+ }}
+ onContextMenu={this.onContextMenu}
style={{
pointerEvents: this.backgroundEvents ? "all" : undefined,
transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
@@ -1364,12 +1390,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
{!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
this.placeholder : this.marqueeView}
<CollectionFreeFormOverlayView elements={this.elementFunc} />
-
+ {this.isAnnotationOverlay || !this.props.isSelected() || this.props.Document._viewType === CollectionViewType.Pile ? (null) :
+ <>
+ <div key="back" className="backKeyframe" onClick={this.prevKeyframe}>
+ <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ </div>
+ <div key="num" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }} onClick={action(() => this.Document.editing = !this.Document.editing)} >
+ {NumCast(this.Document.currentFrame)}
+ </div>
+ <div key="fwd" className="fwdKeyframe" onClick={this.nextKeyframe}>
+ <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ </div>
+ </>}
<div className={"pullpane-indicator"}
style={{
display: this._pullDirection ? "block" : "none",
top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
- // left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto",
width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 492ba6ed6..ed70ac9e8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -20,7 +20,6 @@ import { CollectionView } from "../CollectionView";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import "./MarqueeView.scss";
import React = require("react");
-import { InteractionUtils } from "../../../util/InteractionUtils";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -66,58 +65,69 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
//make textbox and add it to this collection
// tslint:disable-next-line:prefer-const
let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
- if (e.key === ":") {
- DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y);
+ if (e.key === "?") {
+ ContextMenu.Instance.setDefaultItem("?", (str: string) => {
+ const textDoc = Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, {
+ _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 800, isAnnotating: false,
+ title: "bing", UseCors: true
+ });
+ this.props.addDocTab(textDoc, "onRight");
+ });
ContextMenu.Instance.displayMenu(this._downX, this._downY);
- } else if (e.key === "q" && e.ctrlKey) {
- e.preventDefault();
- (async () => {
- const text: string = await navigator.clipboard.readText();
- const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
- for (let i = 0; i < ns.length - 1; i++) {
- while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
- ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
- ns[i].endsWith(".\r") || ns[i].endsWith(".") ||
- ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) {
- const sub = ns[i].endsWith("\r") ? 1 : 0;
- const br = ns[i + 1].trim() === "";
- ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft());
- if (br) break;
+ } else
+ if (e.key === ":") {
+ DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y);
+
+ ContextMenu.Instance.displayMenu(this._downX, this._downY);
+ } else if (e.key === "q" && e.ctrlKey) {
+ e.preventDefault();
+ (async () => {
+ const text: string = await navigator.clipboard.readText();
+ const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
+ for (let i = 0; i < ns.length - 1; i++) {
+ while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
+ ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
+ ns[i].endsWith(".\r") || ns[i].endsWith(".") ||
+ ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) {
+ const sub = ns[i].endsWith("\r") ? 1 : 0;
+ const br = ns[i + 1].trim() === "";
+ ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft());
+ if (br) break;
+ }
+ }
+ ns.map(line => {
+ const indent = line.search(/\S|$/);
+ const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: y, title: line });
+ this.props.addDocument(newBox);
+ y += 40 * this.props.getTransform().Scale;
+ });
+ })();
+ } 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);
}
- }
- ns.map(line => {
- const indent = line.search(/\S|$/);
- const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: y, title: line });
- this.props.addDocument(newBox);
- y += 40 * this.props.getTransform().Scale;
});
- })();
- } 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);
+ } else if (!e.ctrlKey) {
+ FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
+ const tbox = Docs.Create.TextDocument("", {
+ _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
+ _fontFamily: StrCast(Doc.UserDoc().fontFamily), _backgroundColor: StrCast(Doc.UserDoc().backgroundColor),
+ title: "-typed text-"
+ });
+ const template = FormattedTextBox.DefaultLayout;
+ if (template instanceof Doc) {
+ tbox._width = NumCast(template._width);
+ tbox.layoutKey = "layout_" + StrCast(template.title);
+ Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
}
- });
- } else if (!e.ctrlKey) {
- FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
- const tbox = Docs.Create.TextDocument("", {
- _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
- _backgroundColor: StrCast(Doc.UserDoc().backgroundColor),
- title: "-typed text-"
- });
- const template = FormattedTextBox.DefaultLayout;
- if (template instanceof Doc) {
- tbox._width = NumCast(template._width);
- tbox.layoutKey = "layout_" + StrCast(template.title);
- Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template;
+ this.props.addLiveTextDocument(tbox);
}
- this.props.addLiveTextDocument(tbox);
- }
e.stopPropagation();
}
//heuristically converts pasted text into a table.
@@ -324,9 +334,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
_LODdisable: true,
title: "a nested collection",
});
- // const dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
- // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
- // this.marqueeInkDelete(inkData);
+ selected.forEach(d => d.context = newCollection);
this.hideMarquee();
return newCollection;
}
@@ -337,8 +345,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
SelectionManager.DeselectAll();
selected.forEach(d => this.props.removeDocument(d));
const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
- this.props.addDocument(newCollection);
- this.props.selectDocuments([newCollection], []);
+ this.props.addDocument(newCollection!);
+ this.props.selectDocuments([newCollection!], []);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
}
@@ -614,6 +622,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
render() {
return <div className="marqueeView"
style={{ overflow: StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }}
+ onDragOver={e => e.preventDefault()}
onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
{this.props.children}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5d6e587d9..682aed8f5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -10,9 +10,13 @@ import React = require("react");
import { Document } from "../../../fields/documentSchemas";
import { TraceMobx } from "../../../fields/util";
import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
+import { List } from "../../../fields/List";
+import { numberRange } from "../../../Utils";
+import { ComputedField } from "../../../fields/ScriptField";
+import { listSpec } from "../../../fields/Schema";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, highlight?: boolean, z: number, transition?: string } | undefined;
+ dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined;
zIndex?: number;
highlight?: boolean;
@@ -31,10 +35,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const rnd = seed / 233280;
return min + rnd * (max - min);
}
- get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
+ get displayName() { return "CollectionFreeFormDocumentView(" + this.rootDoc.title + ")"; } // this makes mobx trace() statements more descriptive
get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${this.random(-1, 1) * 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 Opacity() { return this.dataProvider ? this.dataProvider.opacity : Cast(this.layoutDoc.opacity, "number", null); }
get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : (this.Document.zIndex || 0); }
get Highlight() { return this.dataProvider?.highlight; }
get width() { return this.props.sizeProvider && this.sizeProvider ? this.sizeProvider.width : this.layoutDoc[WidthSym](); }
@@ -62,6 +67,61 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
return undefined;
}
+
+ public static getValues(doc: Doc, time: number) {
+ const timecode = Math.round(time);
+ return ({
+ x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number),
+ y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number),
+ opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number),
+ });
+ }
+
+ public static setValues(time: number, d: Doc, x?: number, y?: number, opacity?: number) {
+ const timecode = Math.round(time);
+ Cast(d["x-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = x as any as number;
+ Cast(d["y-indexed"], listSpec("number"), [])[Math.max(0, timecode - 1)] = y as any as number;
+ Cast(d["x-indexed"], listSpec("number"), [])[timecode] = x as any as number;
+ Cast(d["y-indexed"], listSpec("number"), [])[timecode] = y as any as number;
+ Cast(d["opacity-indexed"], listSpec("number"), null)[timecode] = opacity as any as number;
+ }
+ public static updateKeyframe(docs: Doc[], time: number) {
+ const timecode = Math.round(time);
+ docs.forEach(doc => {
+ const xindexed = Cast(doc['x-indexed'], listSpec("number"), null);
+ const yindexed = Cast(doc['y-indexed'], listSpec("number"), null);
+ const opacityindexed = Cast(doc['opacity-indexed'], listSpec("number"), null);
+ xindexed?.length <= timecode + 1 && xindexed.push(undefined as any as number);
+ yindexed?.length <= timecode + 1 && yindexed.push(undefined as any as number);
+ opacityindexed?.length <= timecode + 1 && opacityindexed.push(undefined as any as number);
+ doc.transition = "all 1s";
+ });
+ setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010);
+ }
+
+ public static gotoKeyframe(docs: Doc[]) {
+ docs.forEach(doc => doc.transition = "all 1s");
+ setTimeout(() => docs.forEach(doc => doc.transition = undefined), 1010);
+ }
+
+ public static setupKeyframes(docs: Doc[], timecode: number, progressivize: boolean = false) {
+ docs.forEach((doc, i) => {
+ const curTimecode = progressivize ? i : timecode;
+ const xlist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
+ const ylist = new List<number>(numberRange(timecode + 1).map(i => undefined) as any as number[]);
+ const olist = new List<number>(numberRange(timecode + 1).map(t => progressivize && t < i ? 0 : 1));
+ xlist[Math.max(curTimecode - 1, 0)] = xlist[curTimecode] = NumCast(doc.x);
+ ylist[Math.max(curTimecode - 1, 0)] = ylist[curTimecode] = NumCast(doc.y);
+ doc["x-indexed"] = xlist;
+ doc["y-indexed"] = ylist;
+ doc["opacity-indexed"] = olist;
+ doc.activeFrame = ComputedField.MakeFunction("self.context ? (self.context.currentFrame||0) : 0");
+ doc.x = ComputedField.MakeInterpolated("x", "activeFrame");
+ doc.y = ComputedField.MakeInterpolated("y", "activeFrame");
+ doc.opacity = ComputedField.MakeInterpolated("opacity", "activeFrame");
+ });
+ }
+
nudge = (x: number, y: number) => {
this.props.Document.x = NumCast(this.props.Document.x) + x;
this.props.Document.y = NumCast(this.props.Document.y) + y;
@@ -72,14 +132,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.());
getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y).scale(1 / this.contentScaling());
focusDoc = (doc: Doc) => this.props.focus(doc, false);
+ opacity = () => this.Opacity;
NativeWidth = () => this.nativeWidth;
NativeHeight = () => this.nativeHeight;
render() {
TraceMobx();
+ const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
return <div className="collectionFreeFormDocumentView-container"
style={{
boxShadow:
- this.layoutDoc.opacity === 0 ? undefined : // if it's not visible, then no shadow
+ this.Opacity === 0 ? undefined : // if it's not visible, then no shadow
this.layoutDoc.z ? `#9c9396 ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow
this.props.backgroundHalo?.() ? (`${this.props.backgroundColor?.(this.props.Document)} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
this.layoutDoc.isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big
@@ -92,8 +154,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
height: this.height,
zIndex: this.ZInd,
display: this.ZInd === -99 ? "none" : undefined,
- pointerEvents: this.props.Document.isBackground ? "none" : this.props.pointerEvents ? "all" : undefined
+ pointerEvents: this.props.Document.isBackground || this.Opacity === 0 ? "none" : this.props.pointerEvents ? "all" : undefined
}} >
+ {Doc.UserDoc().renderStyle !== "comic" ? (null) :
+ <div style={{ width: "100%", height: "100%", position: "absolute" }}>
+ <svg style={{ transform: `scale(1,${this.props.PanelHeight() / this.props.PanelWidth()})`, transformOrigin: "top left", overflow: "visible" }} viewBox="0 0 12 14">
+ <path d="M 7 0 C 9 -1 13 1 12 4 C 11 10 13 12 10 12 C 6 12 7 13 2 12 Q -1 11 0 8 C 1 4 0 4 0 2 C 0 0 1 0 1 0 C 3 0 3 1 7 0"
+ style={{ stroke: "black", fill: backgroundColor, strokeWidth: 0.2 }} />
+ </svg>
+ </div>}
{!this.props.fitToBox ?
<DocumentView {...this.props}
@@ -102,6 +171,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
backgroundColor={this.props.backgroundColor}
+ opacity={this.opacity}
NativeHeight={this.NativeHeight}
NativeWidth={this.NativeWidth}
PanelWidth={this.panelWidth}
diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss
new file mode 100644
index 000000000..7849c9976
--- /dev/null
+++ b/src/client/views/nodes/ComparisonBox.scss
@@ -0,0 +1,87 @@
+.comparisonBox-interactive, .comparisonBox {
+ border-radius: inherit;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ z-index: 0;
+ pointer-events: none;
+
+ .clip-div {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ overflow: hidden;
+
+ .beforeBox-cont {
+ height: 100%;
+ overflow: hidden;
+ }
+ }
+
+ .slide-bar {
+ position: absolute;
+ height: 100%;
+ width: 3px;
+ display: inline-block;
+ background: white;
+ cursor: ew-resize;
+ .slide-handle {
+ position: absolute;
+ display: none;
+ height: 20px;
+ width: 30px;
+ bottom: 0px;
+ left: -10.5px;
+ .left-handle, .right-handle{
+ width: 15px;
+ }
+ }
+ }
+
+ .afterBox-cont {
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ }
+
+ .clear-button {
+ position: absolute;
+ top: 3px;
+ opacity: 0.8;
+ pointer-events: all;
+ cursor: pointer;
+ }
+
+ .clear-button.before {
+ left: 3px;
+ }
+
+ .clear-button.after {
+ right: 3px;
+ }
+
+ .placeholder {
+ width: 50%;
+ height: 50%;
+ margin-top: 25%;
+ margin-left: 25%;
+
+ .upload-icon {
+ width: 100%;
+ height: 100%;
+ opacity: 0.5;
+ }
+ }
+}
+.comparisonBox-interactive {
+ pointer-events: unset;
+ .slide-bar {
+ .slide-handle {
+ display: flex;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
new file mode 100644
index 000000000..77e07ec0c
--- /dev/null
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -0,0 +1,109 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, runInAction, Lambda, IReactionDisposer } 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 { NumCast, Cast, StrCast } from '../../../fields/Types';
+import { DragManager } from '../../util/DragManager';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { FieldView, FieldViewProps } from './FieldView';
+import "./ComparisonBox.scss";
+import React = require("react");
+import { ContentFittingDocumentView } from './ContentFittingDocumentView';
+import { undoBatch } from '../../util/UndoManager';
+import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
+import { SnappingManager } from '../../util/SnappingManager';
+import { DocumentViewProps } from './DocumentView';
+
+export const comparisonSchema = createSchema({});
+
+type ComparisonDocument = makeInterface<[typeof comparisonSchema, typeof documentSchema]>;
+const ComparisonDocument = makeInterface(comparisonSchema, documentSchema);
+
+@observer
+export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) {
+ 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];
+
+ @observable _animating = "";
+
+ protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
+ this._disposers[disposerId]?.();
+ if (ele) {
+ this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.dropHandler(e, dropEvent, fieldKey), this.layoutDoc);
+ }
+ }
+
+ @undoBatch
+ private dropHandler = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => {
+ event.stopPropagation();
+ const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments;
+ if (droppedDocs?.length) {
+ this.dataDoc[fieldKey] = droppedDocs[0];
+ }
+ }
+
+ private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => {
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, action(() => {
+ this._animating = "all 1s";
+ this.layoutDoc._clipWidth = targetWidth * 100 / this.props.PanelWidth();
+ setTimeout(action(() => this._animating = ""), 1000);
+ }), false);
+ }
+
+ @action
+ private onPointerMove = ({ movementX }: PointerEvent) => {
+ const width = movementX * this.props.ScreenToLocalTransform().Scale + NumCast(this.layoutDoc._clipWidth) / 100 * this.props.PanelWidth();
+ if (width && width > 5 && width < this.props.PanelWidth()) {
+ this.layoutDoc._clipWidth = width * 100 / this.props.PanelWidth();
+ }
+ return false;
+ }
+
+ @undoBatch
+ clearDoc = (e: React.MouseEvent, fieldKey: string) => {
+ e.stopPropagation;
+ delete this.dataDoc[fieldKey];
+ }
+
+ render() {
+ const clipWidth = NumCast(this.layoutDoc._clipWidth) + "%";
+ const childProps: DocumentViewProps = { ...this.props, pointerEvents: false, parentActive: this.props.active };
+ const clearButton = (which: string) => {
+ return <div className={`clear-button ${which}`} onPointerDown={e => e.stopPropagation()} onClick={e => this.clearDoc(e, `${which}Doc`)}>
+ <FontAwesomeIcon className={`clear-button ${which}`} icon={"times"} size="sm" />
+ </div>;
+ };
+ const displayDoc = (which: string) => {
+ const whichDoc = Cast(this.dataDoc[`${which}Doc`], Doc, null);
+ return whichDoc ? <>
+ <ContentFittingDocumentView {...childProps} Document={whichDoc} />
+ {clearButton(which)}
+ </> : // placeholder image if doc is missing
+ <div className="placeholder">
+ <FontAwesomeIcon className="upload-icon" icon={"cloud-upload-alt"} size="lg" />
+ </div>;
+ };
+ const displayBox = (which: string, index: number, cover: number) => {
+ return <div className={`${which}Box-cont`} key={which} style={{ width: this.props.PanelWidth() }}
+ onPointerDown={e => this.registerSliding(e, cover)}
+ ref={ele => this.createDropTarget(ele, `${which}Doc`, index)} >
+ {displayDoc(which)}
+ </div>;
+ };
+
+ return (
+ <div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}`}>
+ {displayBox("after", 1, this.props.PanelWidth() - 5)}
+ <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}>
+ {displayBox("before", 0, 5)}
+ </div>
+
+ <div className="slide-bar" style={{ left: `calc(${clipWidth} - 0.5px)` }}>
+ <div className="slide-handle" />
+ </div>
+ </div >);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index f4785bb0c..ef56e6fcd 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -30,6 +30,7 @@ import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
import { LinkAnchorBox } from "./LinkAnchorBox";
import { PresElementBox } from "../presentationview/PresElementBox";
import { ScreenshotBox } from "./ScreenshotBox";
+import { ComparisonBox } from "./ComparisonBox";
import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
import { InkingStroke } from "../InkingStroke";
@@ -194,7 +195,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
- RecommendationsBox, ScreenshotBox, HTMLtag
+ RecommendationsBox, ScreenshotBox, HTMLtag, ComparisonBox
}}
bindings={bindings}
jsx={layoutFrame}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index dea09cb30..b7726f7ba 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -94,6 +94,7 @@
text-align: center;
text-overflow: ellipsis;
white-space: pre;
+ position: absolute;
}
.documentView-titleWrapper-hover {
display:none;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 95af52f13..b5de0af12 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,7 +12,6 @@ import { listSpec } from "../../../fields/Schema";
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
@@ -92,6 +91,7 @@ export interface DocumentViewProps {
pinToPres: (document: Doc) => void;
backgroundHalo?: () => boolean;
backgroundColor?: (doc: Doc) => string | undefined;
+ opacity?: () => number | undefined;
ChromeHeight?: () => number;
dontRegisterView?: boolean;
layoutKey?: string;
@@ -683,12 +683,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
}
- @undoBatch
- @action
- toggleLockTransform = (): void => {
- this.Document.lockedTransform = this.Document.lockedTransform ? undefined : true;
- }
-
@action
onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
@@ -751,7 +745,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
- moreItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
if (!ClientUtils.RELEASE) {
@@ -765,10 +758,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
}
moreItems.push({
- description: "Download document", icon: "download", event: async () =>
- console.log(JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
+ description: "Download document", icon: "download", event: async () => {
+ const response = await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
- })))
+ });
+ console.log(response ? JSON.parse(response) : undefined);
+ }
// const a = document.createElement("a");
// const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
// a.href = url;
@@ -1117,9 +1112,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}), 400);
});
+ renderLock() {
+ return (this.Document.isBackground !== undefined || this.isSelected(false)) &&
+ ((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) &&
+ this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
+ <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
+ <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
+ </div>
+ : (null);
+ }
+
render() {
if (!(this.props.Document instanceof Doc)) return (null);
- const backgroundColor = StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
+ const backgroundColor = Doc.UserDoc().renderStyle === "comic" ? undefined : StrCast(this.layoutDoc._backgroundColor) || StrCast(this.layoutDoc.backgroundColor) || StrCast(this.Document.backgroundColor) || this.props.backgroundColor?.(this.Document);
+ const opacity = Cast(this.layoutDoc._opacity, "number", Cast(this.layoutDoc.opacity, "number", Cast(this.Document.opacity, "number", null)));
+ const finalOpacity = this.props.opacity ? this.props.opacity() : opacity;
const finalColor = this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc._viewType === CollectionViewType.Linear ? undefined : backgroundColor;
const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
const borderRounding = this.layoutDoc.borderRounding;
@@ -1156,7 +1163,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
boxShadow: this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined,
background: finalColor,
- opacity: this.Document.opacity,
+ opacity: finalOpacity,
fontFamily: StrCast(this.Document._fontFamily, "inherit"),
fontSize: Cast(this.Document._fontSize, "number", null)
}}>
@@ -1165,11 +1172,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
<div className="documentView-contentBlocker" />
</> :
this.innards}
- {(this.Document.isBackground !== undefined || this.isSelected(false)) && this.props.renderDepth > 0 && this.props.PanelWidth() > 0 ?
- <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
- <FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
- </div>
- : (null)}
+ {this.renderLock()}
</div>;
{ this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 47e7607d6..6913dfbc7 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -19,7 +19,6 @@ import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_ser
import { Docs } from '../../documents/Documents';
import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
-import { SelectionManager } from '../../util/SelectionManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../../views/ContextMenu";
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
@@ -267,7 +266,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
if (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc) {
requestImageSize(imgPath).then(action((inquiredSize: any) => {
const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]) % 180;
- const rotatedNativeSize = rotation === 90 || rotation === 270 ? { height: inquiredSize.width, width: inquiredSize.height } : inquiredSize;
+ const rotatedNativeSize = { width: inquiredSize.width, height: inquiredSize.height };
+ if (inquiredSize.orientation === 6 || rotation === 90 || rotation === 270) {
+ rotatedNativeSize.width = inquiredSize.height;
+ rotatedNativeSize.height = inquiredSize.width;
+ }
const rotatedAspect = rotatedNativeSize.height / rotatedNativeSize.width;
if (this.layoutDoc[WidthSym]() && (!cachedNativeSize.width || !cachedNativeSize.height || Math.abs(1 - docAspect / rotatedAspect) > 0.1)) {
this.layoutDoc._height = this.layoutDoc[WidthSym]() * rotatedAspect;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 956d6556b..3cbe3e494 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -99,9 +99,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
<div className="keyValuePair-td-key-container">
<button style={hover} className="keyValuePair-td-key-delete" onClick={undoBatch(() => {
if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
- props.Document[props.fieldKey] = undefined;
+ delete props.Document[props.fieldKey];
}
- else props.Document.proto![props.fieldKey] = undefined;
+ else delete props.Document.proto![props.fieldKey];
})}>
X
</button>
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 2d27ec441..ad9e49369 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,5 +1,3 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit } from '@fortawesome/free-regular-svg-icons';
import { action } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -16,9 +14,6 @@ import { ViewBoxBaseComponent } from '../DocComponent';
import { FieldView, FieldViewProps } from './FieldView';
import './LabelBox.scss';
-
-library.add(faEdit as any);
-
const LabelSchema = createSchema({});
type LabelDocument = makeInterface<[typeof LabelSchema, typeof documentSchema]>;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index 098aa58e9..83245a89c 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -72,7 +72,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
anchorContainerDoc && this.props.bringToFront(anchorContainerDoc, false);
if (anchorContainerDoc && !this.layoutDoc.onClick && !this._isOpen) {
this._timeout = setTimeout(action(() => {
- DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, "inTab")), false);
+ DocumentManager.Instance.FollowLink(this.rootDoc, anchorContainerDoc, document => this.props.addDocTab(document, StrCast(this.layoutDoc.linkOpenLocation, e.altKey ? "inTab" : "onRight")), false);
this._editing = false;
}), 300 - (Date.now() - this._lastTap));
}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 342a8a215..05306c29f 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -1,11 +1,11 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, DocCastAsync } from "../../../fields/Doc";
import { InkTool } from "../../../fields/InkField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { returnFalse } from "../../../Utils";
+import { returnFalse, returnOne } from "../../../Utils";
import { documentSchema } from "../../../fields/documentSchemas";
import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
@@ -16,7 +16,6 @@ import { FieldView, FieldViewProps } from './FieldView';
import "./PresBox.scss";
import { ViewBoxBaseComponent } from "../DocComponent";
import { makeInterface } from "../../../fields/Schema";
-import { List } from "../../../fields/List";
import { Docs } from "../../documents/Documents";
import { PrefetchProxy } from "../../../fields/Proxy";
import { ScriptField } from "../../../fields/ScriptField";
@@ -59,7 +58,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@action
next = () => {
this.updateCurrentPresentation();
- if (this.childDocs[this.itemIndex + 1] !== undefined) {
+ const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
+ const lastFrame = Cast(presTargetDoc.lastFrame, "number", null);
+ const curFrame = NumCast(presTargetDoc.currentFrame);
+ if (lastFrame !== undefined && curFrame < lastFrame) {
+ presTargetDoc.currentFrame = curFrame + 1;
+ }
+ else if (this.childDocs[this.itemIndex + 1] !== undefined) {
let nextSelected = this.itemIndex + 1;
this.gotoDocument(nextSelected, this.itemIndex);
@@ -188,11 +193,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
//The function that is called when a document is clicked or reached through next or back.
//it'll also execute the necessary actions if presentation is playing.
- public gotoDocument = (index: number, fromDoc: number) => {
+ public gotoDocument = action((index: number, fromDoc: number) => {
this.updateCurrentPresentation();
Doc.UnBrushAllDocs();
if (index >= 0 && index < this.childDocs.length) {
this.rootDoc._itemIndex = index;
+ const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null);
+ if (presTargetDoc.lastFrame !== undefined) {
+ presTargetDoc.currentFrame = 0;
+ }
if (!this.layoutDoc.presStatus) {
this.layoutDoc.presStatus = true;
@@ -203,7 +212,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
this.hideIfNotPresented(index);
this.showAfterPresented(index);
}
- }
+ });
//The function that starts or resets presentaton functionally, depending on status flag.
startOrResetPres = () => {
@@ -286,7 +295,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
(this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
render() {
- this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.map((child, i) => child));
+ // console.log("render = " + this.layoutDoc.title + " " + this.layoutDoc.presStatus);
+ // const presOrderedDocs = DocListCast(this.rootDoc.presOrderedDocs);
+ // if (presOrderedDocs.length != this.childDocs.length || presOrderedDocs.some((pd, i) => pd !== this.childDocs[i])) {
+ // this.rootDoc.presOrderedDocs = new List<Doc>(this.childDocs.slice());
+ // }
+ this.childDocs.slice(); // needed to insure that the childDocs are loaded for looking up fields
const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
return <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} >
<div className="presBox-buttons" style={{ display: this.rootDoc._chromeStatus === "disabled" ? "none" : undefined }}>
@@ -316,6 +330,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
PanelWidth={this.props.PanelWidth}
PanelHeight={this.panelHeight}
moveDocument={returnFalse}
+ childOpacity={returnOne}
childLayoutTemplate={this.childLayoutTemplate}
filterAddDocument={this.addDocumentFilter}
removeDocument={returnFalse}
@@ -333,5 +348,6 @@ Scripting.addGlobal(function lookupPresBoxField(container: Doc, field: string, d
if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 50 : 46;
if (field === 'presStatus') return container.presStatus;
if (field === '_itemIndex') return container._itemIndex;
+ if (field === 'presBox') return container;
return undefined;
});
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 5d4af2d77..29e3c008a 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -6,7 +6,7 @@ import { action, computed, IReactionDisposer, observable, runInAction } from "mo
import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
import { Cast, NumCast } from "../../../fields/Types";
import { VideoField } from "../../../fields/URLField";
import { emptyFunction, returnFalse, returnOne, Utils, returnZero } from "../../../Utils";
@@ -18,6 +18,8 @@ import { ViewBoxBaseComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./ScreenshotBox.scss";
+import { Doc, WidthSym, HeightSym } from "../../../fields/Doc";
+import { OverlayView } from "../OverlayView";
const path = require('path');
type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
@@ -72,7 +74,14 @@ export class ScreenshotBox extends ViewBoxBaseComponent<FieldViewProps, Screensh
x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y),
_width: 150, _height: height / width * 150, title: "--screenshot--"
});
- this.props.addDocument?.(imageSummary);
+ if (!this.props.addDocument || this.props.addDocument === returnFalse) {
+ const spt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ imageSummary.x = spt[0];
+ imageSummary.y = spt[1];
+ Cast(Cast(Doc.UserDoc().myOverlayDocuments, Doc, null)?.data, listSpec(Doc), []).push(imageSummary);
+ } else {
+ this.props.addDocument?.(imageSummary);
+ }
}
}, 500);
});
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ccf1f5588..9d02239fc 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,6 +1,4 @@
import React = require("react");
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faVideo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx";
import { observer } from "mobx-react";
@@ -8,7 +6,6 @@ import * as rp from 'request-promise';
import { Doc } from "../../../fields/Doc";
import { InkTool } from "../../../fields/InkField";
import { createSchema, makeInterface } from "../../../fields/Schema";
-import { ScriptField } from "../../../fields/ScriptField";
import { Cast, StrCast } from "../../../fields/Types";
import { VideoField } from "../../../fields/URLField";
import { Utils, emptyFunction, returnOne, returnZero } from "../../../Utils";
@@ -22,6 +19,7 @@ import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./VideoBox.scss";
import { documentSchema } from "../../../fields/documentSchemas";
+import { Networking } from "../../Network";
const path = require('path');
export const timeSchema = createSchema({
@@ -30,8 +28,6 @@ export const timeSchema = createSchema({
type VideoDocument = makeInterface<[typeof documentSchema, typeof timeSchema]>;
const VideoDocument = makeInterface(documentSchema, timeSchema);
-library.add(faVideo);
-
@observer
export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
static _youtubeIframeCounter: number = 0;
@@ -104,41 +100,59 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
canvas.height = 640 * (this.layoutDoc._nativeHeight || 0) / (this.layoutDoc._nativeWidth || 1);
const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
if (ctx) {
- ctx.rect(0, 0, canvas.width, canvas.height);
- ctx.fillStyle = "blue";
- ctx.fill();
+ // ctx.rect(0, 0, canvas.width, canvas.height);
+ // ctx.fillStyle = "blue";
+ // ctx.fill();
this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
}
- if (!this._videoRef) { // can't find a way to take snapshots of videos
- const b = Docs.Create.ButtonDocument({
+ if (!this._videoRef) {
+ const b = Docs.Create.LabelDocument({
x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 1),
- _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString()
+ _width: 150, _height: 50, title: (this.layoutDoc.currentTimecode || 0).toString(),
+ });
+ b.isLinkButton = true;
+ this.props.addDocument?.(b);
+ DocUtils.MakeLink({ doc: b }, { doc: this.rootDoc }, "video snapshot");
+ Networking.PostToServer("/youtubeScreenshot", {
+ id: this.youtubeVideoId,
+ timecode: this.layoutDoc.currentTimecode
+ }).then(response => {
+ const resolved = response?.accessPaths?.agnostic?.client;
+ if (resolved) {
+ this.props.removeDocument?.(b);
+ this.createRealSummaryLink(resolved);
+ }
});
- b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.layoutDoc.currentTimecode || 0)}`);
} else {
//convert to desired file format
const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.rootDoc.title).replace(/\..*$/, "") + "_" + (this.layoutDoc.currentTimecode || 0).toString().replace(/\./, "_")));
- VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+ VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => {
if (returnedFilename) {
- const url = this.choosePath(Utils.prepend(returnedFilename));
- const imageSummary = Docs.Create.ImageDocument(url, {
- _nativeWidth: this.layoutDoc._nativeWidth, _nativeHeight: this.layoutDoc._nativeHeight,
- x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0),
- _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc.currentTimecode || 0) + " image-"
- });
- Doc.GetProto(imageSummary)["data-nativeWidth"] = this.layoutDoc._nativeWidth;
- Doc.GetProto(imageSummary)["data-nativeHeight"] = this.layoutDoc._nativeHeight;
- imageSummary.isLinkButton = true;
- this.props.addDocument && this.props.addDocument(imageSummary);
- DocUtils.MakeLink({ doc: imageSummary }, { doc: this.rootDoc }, "video snapshot");
+ this.createRealSummaryLink(returnedFilename);
}
});
}
}
+ private createRealSummaryLink = (relative: string) => {
+ const url = this.choosePath(Utils.prepend(relative));
+ const width = (this.layoutDoc._width || 0);
+ const height = (this.layoutDoc._height || 0);
+ const imageSummary = Docs.Create.ImageDocument(url, {
+ _nativeWidth: this.layoutDoc._nativeWidth, _nativeHeight: this.layoutDoc._nativeHeight,
+ x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0),
+ _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc.currentTimecode || 0) + " image-"
+ });
+ Doc.GetProto(imageSummary)["data-nativeWidth"] = this.layoutDoc._nativeWidth;
+ Doc.GetProto(imageSummary)["data-nativeHeight"] = this.layoutDoc._nativeHeight;
+ imageSummary.isLinkButton = true;
+ this.props.addDocument?.(imageSummary);
+ DocUtils.MakeLink({ doc: imageSummary }, { doc: this.rootDoc }, "video snapshot");
+ }
+
@action
updateTimecode = () => {
this.player && (this.layoutDoc.currentTimecode = this.player.currentTime);
@@ -162,8 +176,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
componentWillUnmount() {
this.Pause();
- this._reactionDisposer && this._reactionDisposer();
- this._youtubeReactionDisposer && this._youtubeReactionDisposer();
+ this._reactionDisposer?.();
+ this._youtubeReactionDisposer?.();
}
@action
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 82f05012a..a91d4dfd9 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,12 +1,12 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faStickyNote, faPen, faMousePointer } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, observable, trace, IReactionDisposer, reaction } from "mobx";
+import { action, computed, observable, trace, IReactionDisposer, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, FieldResult } from "../../../fields/Doc";
+import { Doc, FieldResult, DocListCast } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
import { HtmlField } from "../../../fields/HtmlField";
import { InkTool } from "../../../fields/InkField";
-import { makeInterface } from "../../../fields/Schema";
+import { makeInterface, listSpec } from "../../../fields/Schema";
import { Cast, NumCast, BoolCast, StrCast } from "../../../fields/Types";
import { WebField } from "../../../fields/URLField";
import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
@@ -22,6 +22,10 @@ import React = require("react");
import * as WebRequest from 'web-request';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { undoBatch } from "../../util/UndoManager";
+import { List } from "../../../fields/List";
const htmlToText = require("html-to-text");
library.add(faStickyNote);
@@ -33,7 +37,7 @@ const WebDocument = makeInterface(documentSchema);
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
- get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) === "disabled"; }
+ get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; }
set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; }
@observable private _url: string = "hello";
@observable private _pressX: number = 0;
@@ -48,19 +52,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
iframeLoaded = action((e: any) => {
- if (this._iframeRef.current?.contentDocument) {
- this._iframeRef.current.contentDocument.addEventListener('pointerdown', this.iframedown, false);
- this._iframeRef.current.contentDocument.addEventListener('scroll', this.iframeScrolled, false);
- this.layoutDoc.scrollHeight = this._iframeRef.current.contentDocument.children?.[0].scrollHeight || 1000;
- this._iframeRef.current.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
+ const iframe = this._iframeRef.current;
+ if (iframe && iframe.contentDocument) {
+ iframe.setAttribute("enable-annotation", "true");
+ iframe.contentDocument.addEventListener('pointerdown', this.iframedown, false);
+ iframe.contentDocument.addEventListener('scroll', this.iframeScrolled, false);
+ this.layoutDoc.scrollHeight = iframe.contentDocument.children?.[0].scrollHeight || 1000;
+ iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
+ iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc.scrollLeft);
}
this._reactionDisposer?.();
- this._reactionDisposer = reaction(() => this.layoutDoc.scrollY,
- (scrollY) => {
- if (scrollY !== undefined) {
- this._outerRef.current!.scrollTop = scrollY;
+ this._reactionDisposer = reaction(() => ({ y: this.layoutDoc.scrollY, x: this.layoutDoc.scrollX }),
+ ({ x, y }) => {
+ if (y !== undefined) {
+ this._outerRef.current!.scrollTop = y;
this.layoutDoc.scrollY = undefined;
}
+ if (x !== undefined) {
+ this._outerRef.current!.scrollLeft = x;
+ this.layoutDoc.scrollX = undefined;
+ }
},
{ fireImmediately: true }
);
@@ -70,14 +81,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this._setPreviewCursor?.(e.screenX, e.screenY, false);
}
iframeScrolled = (e: any) => {
- const scroll = e.target?.children?.[0].scrollTop;
- this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scroll;
+ const scrollTop = e.target?.children?.[0].scrollTop;
+ const scrollLeft = e.target?.children?.[0].scrollLeft;
+ this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scrollTop;
+ this.layoutDoc.scrollLeft = this._outerRef.current!.scrollLeft = scrollLeft;
}
async componentDidMount() {
-
- this.setURL();
-
- this._iframeRef.current!.setAttribute("enable-annotation", "true");
+ const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
+ runInAction(() => this._url = urlField?.url.toString() || "");
document.addEventListener("pointerup", this.onLongPressUp);
document.addEventListener("pointermove", this.onLongPressMove);
@@ -86,14 +97,18 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
const youtubeaspect = 400 / 315;
const nativeWidth = NumCast(this.layoutDoc._nativeWidth);
const nativeHeight = NumCast(this.layoutDoc._nativeHeight);
- if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
- if (!nativeWidth) this.layoutDoc._nativeWidth = 600;
- this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect;
- this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
- }
+ if (field) {
+ if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
+ if (!nativeWidth) this.layoutDoc._nativeWidth = 600;
+ this.layoutDoc._nativeHeight = NumCast(this.layoutDoc._nativeWidth) / youtubeaspect;
+ this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect;
+ }
+ } // else it's an HTMLfield
} else if (field?.url) {
const result = await WebRequest.get(Utils.CorsProxy(field.url.href));
- this.dataDoc.text = htmlToText.fromString(result.content);
+ if (result) {
+ this.dataDoc.text = htmlToText.fromString(result.content);
+ }
}
}
@@ -101,8 +116,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this._reactionDisposer?.();
document.removeEventListener("pointerup", this.onLongPressUp);
document.removeEventListener("pointermove", this.onLongPressMove);
- this._iframeRef.current!.contentDocument?.removeEventListener('pointerdown', this.iframedown);
- this._iframeRef.current!.contentDocument?.removeEventListener('scroll', this.iframeScrolled);
+ this._iframeRef.current?.contentDocument?.removeEventListener('pointerdown', this.iframedown);
+ this._iframeRef.current?.contentDocument?.removeEventListener('scroll', this.iframeScrolled);
}
@action
@@ -110,16 +125,73 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
this._url = e.target.value;
}
+ onUrlDragover = (e: React.DragEvent) => {
+ e.preventDefault();
+ }
@action
- submitURL = () => {
- this.dataDoc[this.props.fieldKey] = new WebField(new URL(this._url));
+ onUrlDrop = (e: React.DragEvent) => {
+ const { dataTransfer } = e;
+ const html = dataTransfer.getData("text/html");
+ const uri = dataTransfer.getData("text/uri-list");
+ const url = uri || html || this._url;
+ this._url = url.startsWith(window.location.origin) ?
+ url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url;
+ this.submitURL();
+ e.stopPropagation();
}
@action
- setURL() {
- const urlField: FieldResult<WebField> = Cast(this.dataDoc[this.props.fieldKey], WebField);
- if (urlField) this._url = urlField.url.toString();
- else this._url = "";
+ forward = () => {
+ const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
+ const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
+ if (future.length) {
+ history.push(this._url);
+ this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
+ this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = future.pop()!));
+ this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)]));
+ }
+ }
+
+ @action
+ back = () => {
+ const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
+ const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
+ if (history.length) {
+ if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
+ else future.push(this._url);
+ this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey]));
+ this.dataDoc[this.fieldKey] = new WebField(new URL(this._url = history.pop()!));
+ this.dataDoc[this.annotationKey] = new List<Doc>(DocListCast(this.dataDoc[this.annotationKey + "-" + this.urlHash(this._url)]));
+ }
+ }
+
+ urlHash(s: string) {
+ return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0);
+ }
+ @action
+ submitURL = () => {
+ if (!this._url.startsWith("http")) this._url = "http://" + this._url;
+ try {
+ const URLy = new URL(this._url);
+ const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), null);
+ const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), null);
+ const annos = DocListCast(this.dataDoc[this.annotationKey]);
+ const url = Cast(this.dataDoc[this.fieldKey], WebField, null)?.url.toString();
+ if (url) {
+ if (history === undefined) {
+ this.dataDoc[this.fieldKey + "-history"] = new List<string>([url]);
+
+ } else {
+ history.push(url);
+ }
+ future && (future.length = 0);
+ this.dataDoc[this.annotationKey + "-" + this.urlHash(url)] = new List<Doc>(annos);
+ }
+ this.dataDoc[this.fieldKey] = new WebField(URLy);
+ this.dataDoc[this.annotationKey] = new List<Doc>([]);
+ } catch (e) {
+ console.log("Error in URL :" + this._url);
+ }
}
onValueKeyDown = async (e: React.KeyboardEvent) => {
@@ -130,19 +202,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
toggleAnnotationMode = () => {
- if (!this.layoutDoc.isAnnotating) {
- this.layoutDoc.lockedTransform = false;
- this.layoutDoc.isAnnotating = true;
- }
- else {
- this.layoutDoc.lockedTransform = true;
- this.layoutDoc.isAnnotating = false;
- }
+ this.layoutDoc.isAnnotating = !this.layoutDoc.isAnnotating;
}
urlEditor() {
return (
- <div className="webBox-urlEditor" style={{ top: this._collapsed ? -70 : 0 }}>
+ <div className="webBox-urlEditor"
+ onDrop={this.onUrlDrop}
+ onDragOver={this.onUrlDragover} style={{ top: this._collapsed ? -70 : 0 }}>
<div className="urlEditor">
<div className="editorBase">
<button className="editor-collapse"
@@ -155,7 +222,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
title="Collapse Url Editor" onClick={this.toggleCollapse}>
<FontAwesomeIcon icon="caret-up" size="2x" />
</button>
- <div className="webBox-buttons" style={{ display: this._collapsed ? "none" : "flex" }}>
+ <div className="webBox-buttons"
+ onDrop={this.onUrlDrop}
+ onDragOver={this.onUrlDragover} style={{ display: this._collapsed ? "none" : "flex" }}>
<div className="webBox-freeze" title={"Annotate"} style={{ background: this.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} onClick={this.toggleAnnotationMode} >
<FontAwesomeIcon icon={faPen} size={"2x"} />
</div>
@@ -165,6 +234,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
<input className="webpage-urlInput"
placeholder="ENTER URL"
value={this._url}
+ onDrop={this.onUrlDrop}
+ onDragOver={this.onUrlDragover}
onChange={this.onURLChange}
onKeyDown={this.onValueKeyDown}
/>
@@ -172,10 +243,17 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
- minWidth: "100px",
+ maxWidth: "120px",
}}>
- <button className="submitUrl" onClick={this.submitURL}>
- SUBMIT
+ <button className="submitUrl" onClick={this.submitURL}
+ onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}>
+ GO
+ </button>
+ <button className="submitUrl" onClick={this.back}>
+ <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon>
+ </button>
+ <button className="submitUrl" onClick={this.forward}>
+ <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon>
</button>
</div>
</div>
@@ -308,6 +386,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
}
+
+ @undoBatch
+ @action
+ toggleNativeDimensions = () => {
+ Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight());
+ }
+ specificContextMenu = (e: React.MouseEvent): void => {
+ const cm = ContextMenu.Instance;
+ const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
+ cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
+
+ }
+
//const href = "https://brown365-my.sharepoint.com/personal/bcz_ad_brown_edu/_layouts/15/Doc.aspx?sourcedoc={31aa3178-4c21-4474-b367-877d0a7135e4}&action=embedview&wdStartOn=1";
@computed
@@ -348,7 +440,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
{this.urlEditor()}
</>);
}
- scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.Document.scrollTop));
+ scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc.scrollLeft), NumCast(this.layoutDoc.scrollTop));
render() {
return (<div className={`webBox-container`}
style={{
@@ -356,18 +448,27 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
- }} >
+ }}
+ onContextMenu={this.specificContextMenu}>
+ <base target="_blank" />
{this.content}
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{ pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none" }}
onWheel={e => e.stopPropagation()}
onScroll={e => {
- if (this._iframeRef.current!.contentDocument!.children[0].scrollTop !== this._outerRef.current!.scrollTop) {
- this._iframeRef.current!.contentDocument!.children[0].scrollTop = this._outerRef.current!.scrollTop;
+ const iframe = this._iframeRef?.current?.contentDocument;
+ const outerFrame = this._outerRef.current;
+ if (iframe && outerFrame) {
+ if (iframe.children[0].scrollTop !== outerFrame.scrollTop) {
+ iframe.children[0].scrollTop = outerFrame.scrollTop;
+ }
+ if (iframe.children[0].scrollLeft !== outerFrame.scrollLeft) {
+ iframe.children[0].scrollLeft = outerFrame.scrollLeft;
+ }
}
//this._outerRef.current!.scrollTop !== this._scrollTop && (this._outerRef.current!.scrollTop = this._scrollTop)
}}>
- <div className={"webBox-innerContent"} style={{ height: NumCast(this.layoutDoc.scrollHeight) }}>
+ <div className={"webBox-innerContent"} style={{ height: NumCast(this.layoutDoc.scrollHeight), width: 4000 }}>
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5e33e7e70..fc131cd38 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -206,6 +206,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
if (json !== curLayout?.Data) {
!curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize));
+ !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily));
this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText);
this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited
}
@@ -868,7 +869,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type.name === "link");
- const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
+ const link = view.state.schema.mark(view.state.schema.marks.link, { href: Utils.prepend(`/doc/${linkId}`), location: "onRight", title: title, docref: true });
marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
@@ -1178,7 +1179,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
@action
tryUpdateHeight(limitHeight?: number) {
let scrollHeight = this._ref.current?.scrollHeight;
- if (this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ if (this.props.renderDepth && this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight && scrollHeight) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
scrollHeight = scrollHeight * NumCast(this.layoutDoc.scale, 1);
if (limitHeight && scrollHeight > limitHeight) {
scrollHeight = limitHeight;
@@ -1223,14 +1224,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
transform: `scale(${scale})`,
transformOrigin: "top left",
width: `${100 / scale}%`,
- height: `${100 / scale}%`,
+ height: `calc(${100 / scale}% - ${this.props.ChromeHeight?.() || 0}px)`,
...this.styleFromLayoutString(scale)
}}>
<div className={`formattedTextBox-cont`} ref={this._ref}
style={{
width: "100%",
- height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : `calc(100% - ${this.props.ChromeHeight?.() || 0}px`,
- background: this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
+ height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined,
+ background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""),
opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
pointerEvents: interactive ? "none" : undefined,
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 2f7d23021..0e3e7f91e 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -154,15 +154,12 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const originalDoc = layoutDoc.rootDocument || layoutDoc;
if (originalDoc instanceof Doc) {
const layoutKey = StrCast(originalDoc.layoutKey);
- const newDoc = Docs.Create.TextDocument("", {
- layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
- layoutKey,
- _singleLine: BoolCast(originalDoc._singleLine),
- x: NumCast(originalDoc.x), y: NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10, _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
- });
+ const newDoc = Doc.MakeCopy(originalDoc, true);
+ newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
}
+ Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
}
@@ -178,15 +175,12 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
const originalDoc = layoutDoc.rootDocument || layoutDoc;
if (force || props.Document._singleLine) {
const layoutKey = StrCast(originalDoc.layoutKey);
- const newDoc = Docs.Create.TextDocument("", {
- layout: Cast(originalDoc.layout, Doc, null) || FormattedTextBox.DefaultLayout,
- layoutKey,
- _singleLine: BoolCast(originalDoc._singleLine),
- x: NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10, y: NumCast(originalDoc.y), _width: NumCast(layoutDoc._width), _height: NumCast(layoutDoc._height)
- });
+ const newDoc = Doc.MakeCopy(originalDoc, true);
+ newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10;
if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) {
newDoc[layoutKey] = originalDoc[layoutKey];
}
+ Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
props.addDocument(newDoc);
return true;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c50969493..810ce5aea 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -125,7 +125,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
_lastSearch: string = "";
componentDidMount = async () => {
- !this.props.Document.lockedTransform && (this.props.Document.lockedTransform = true);
// change the address to be the file address of the PNG version of each page
// file address of the pdf
const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!;
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 280ba9093..475fef5b2 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -4,9 +4,9 @@ import { observer } from "mobx-react";
import { Doc, DataSym, DocListCast } from "../../../fields/Doc";
import { documentSchema } from '../../../fields/documentSchemas';
import { Id } from "../../../fields/FieldSymbols";
-import { createSchema, makeInterface } from '../../../fields/Schema';
+import { createSchema, makeInterface, listSpec } from '../../../fields/Schema';
import { Cast, NumCast, BoolCast, ScriptCast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero } from "../../../Utils";
+import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, returnZero, numberRange } from "../../../Utils";
import { Transform } from "../../util/Transform";
import { CollectionViewType } from '../collections/CollectionView';
import { ViewBoxBaseComponent } from '../DocComponent';
@@ -14,6 +14,7 @@ import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'
import { FieldView, FieldViewProps } from '../nodes/FieldView';
import "./PresElementBox.scss";
import React = require("react");
+import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
export const presSchema = createSchema({
presentationTargetDoc: Doc,
@@ -43,6 +44,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
@computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation elemnt template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
@computed get presStatus() { return BoolCast(this.lookupField("presStatus")); }
@computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); }
+ @computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); }
@computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
componentDidMount() {
@@ -93,6 +95,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
}
}
+ @action
+ progressivize = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ this.rootDoc.presProgressivize = !this.rootDoc.presProgressivize;
+ const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null);
+ const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
+ if (this.rootDoc.presProgressivize) {
+ rootTarget.currentFrame = 0;
+ CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
+ rootTarget.lastFrame = docs.length - 1;
+ }
+ }
+
/**
* The function that is called on click to turn fading document after presented option on/off.
* It also makes sure that the option swithches from hide-after to this one, since both
@@ -177,6 +192,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
focus={emptyFunction}
whenActiveChanged={returnFalse}
bringToFront={returnFalse}
+ opacity={returnOne}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
ContentScaling={returnOne}
@@ -199,7 +215,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
<strong className="presElementBox-name">
{`${this.indexInPres + 1}. ${this.targetDoc?.title}`}
</strong>
- <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => this.props.removeDocument?.(this.rootDoc)}>X</button>
+ <button className="presElementBox-closeIcon" onPointerDown={e => e.stopPropagation()} onClick={e => {
+ this.props.removeDocument?.(this.rootDoc);
+ e.stopPropagation();
+ }}>X</button>
<br />
</>}
<div className="presElementBox-buttons">
@@ -209,6 +228,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
<button title="Fade After" className={pbi + (this.rootDoc.presFadeButton ? "-selected" : "")} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Hide After" className={pbi + (this.rootDoc.presHideAfterButton ? "-selected" : "")} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={"file-download"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Group With Up" className={pbi + (this.rootDoc.presGroupButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presGroupButton = !this.rootDoc.presGroupButton; }}><FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} /></button>
+ <button title="Progressivize" className={pbi + (this.rootDoc.pres ? "-selected" : "")} onClick={this.progressivize}><FontAwesomeIcon icon={"tasks"} onPointerDown={e => e.stopPropagation()} /></button>
<button title="Expand Inline" className={pbi + (this.rootDoc.presExpandInlineButton ? "-selected" : "")} onClick={e => { e.stopPropagation(); this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton; }}><FontAwesomeIcon icon={"arrow-down"} onPointerDown={e => e.stopPropagation()} /></button>
</div>
{this.renderEmbeddedInline}
diff --git a/src/client/views/search/CheckBox.tsx b/src/client/views/search/CheckBox.tsx
index 8c97d5dbc..0a1e551ec 100644
--- a/src/client/views/search/CheckBox.tsx
+++ b/src/client/views/search/CheckBox.tsx
@@ -2,7 +2,6 @@ import * as React from 'react';
import { observer } from 'mobx-react';
import { observable, action, runInAction, IReactionDisposer, reaction } from 'mobx';
import "./CheckBox.scss";
-import * as anime from 'animejs';
interface CheckBoxProps {
originalStatus: boolean;
diff --git a/src/client/views/webcam/WebCamLogic.js b/src/client/views/webcam/WebCamLogic.js
index f542fb983..a8a2f5fa4 100644
--- a/src/client/views/webcam/WebCamLogic.js
+++ b/src/client/views/webcam/WebCamLogic.js
@@ -1,5 +1,8 @@
'use strict';
import io from "socket.io-client";
+import {
+ resolvedPorts
+} from "../Main";
var socket;
var isChannelReady = false;
@@ -29,7 +32,7 @@ export function initialize(roomName, handlerUI) {
room = roomName;
- socket = io.connect(`${window.location.protocol}//${window.location.hostname}:${4321}`);
+ socket = io.connect(`${window.location.protocol}//${window.location.hostname}:${resolvedPorts.socket}`);
if (room !== '') {
socket.emit('create or join', room);