aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-10-09 16:12:19 -0400
committerSam Wilkins <samwilkins333@gmail.com>2019-10-09 16:12:19 -0400
commitaab01f00b6744032c46213011ecce0e79fcf3dd8 (patch)
tree1bb655974feced0cbc0f00b8cb5269537c8ff2d0 /src
parent6411b7fd6b782957050535850154b05c629fd95a (diff)
parent7763ddefcd14986573f9a0010c7691fa4715b94e (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into exif
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts26
-rw-r--r--src/client/apis/AuthenticationManager.tsx90
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts37
-rw-r--r--src/client/documents/Documents.ts34
-rw-r--r--src/client/util/DictationManager.ts1
-rw-r--r--src/client/util/DocumentManager.ts132
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts4
-rw-r--r--src/client/util/RichTextRules.ts78
-rw-r--r--src/client/util/RichTextSchema.tsx227
-rw-r--r--src/client/util/SearchUtil.ts35
-rw-r--r--src/client/util/SelectionManager.ts2
-rw-r--r--src/client/util/SharingManager.scss4
-rw-r--r--src/client/util/SharingManager.tsx130
-rw-r--r--src/client/util/TooltipTextMenu.tsx85
-rw-r--r--src/client/views/DocumentButtonBar.tsx3
-rw-r--r--src/client/views/DocumentDecorations.tsx47
-rw-r--r--src/client/views/InkingCanvas.tsx6
-rw-r--r--src/client/views/InkingControl.tsx3
-rw-r--r--src/client/views/Main.scss7
-rw-r--r--src/client/views/MainOverlayTextBox.scss29
-rw-r--r--src/client/views/MainOverlayTextBox.tsx155
-rw-r--r--src/client/views/MainView.tsx84
-rw-r--r--src/client/views/MetadataEntryMenu.tsx2
-rw-r--r--src/client/views/OverlayView.tsx11
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx13
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx11
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx6
-rw-r--r--src/client/views/collections/CollectionSubView.tsx1
-rw-r--r--src/client/views/collections/CollectionTreeView.scss2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx2
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx17
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss7
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx6
-rw-r--r--src/client/views/linking/LinkFollowBox.tsx87
-rw-r--r--src/client/views/nodes/Annotation.tsx117
-rw-r--r--src/client/views/nodes/DocumentView.scss1
-rw-r--r--src/client/views/nodes/DocumentView.tsx72
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss29
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx472
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx10
-rw-r--r--src/client/views/nodes/IconBox.tsx6
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.scss4
-rw-r--r--src/client/views/nodes/PDFBox.tsx33
-rw-r--r--src/client/views/nodes/PresBox.tsx33
-rw-r--r--src/client/views/nodes/VideoBox.tsx57
-rw-r--r--src/client/views/pdf/Annotation.scss3
-rw-r--r--src/client/views/pdf/Annotation.tsx27
-rw-r--r--src/client/views/pdf/PDFMenu.tsx10
-rw-r--r--src/client/views/pdf/PDFViewer.scss27
-rw-r--r--src/client/views/pdf/PDFViewer.tsx285
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx32
-rw-r--r--src/client/views/search/SearchBox.scss9
-rw-r--r--src/client/views/search/SearchBox.tsx11
-rw-r--r--src/client/views/search/SearchItem.scss13
-rw-r--r--src/client/views/search/SearchItem.tsx19
-rw-r--r--src/new_fields/Doc.ts33
-rw-r--r--src/new_fields/InkField.ts2
-rw-r--r--src/server/DashUploadUtils.ts2
-rw-r--r--src/server/RouteStore.ts3
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts79
-rw-r--r--src/server/apis/google/GooglePhotosUploadUtils.ts4
-rw-r--r--src/server/authentication/models/current_user_utils.ts22
-rw-r--r--src/server/index.ts31
66 files changed, 1474 insertions, 1370 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 4b892aa70..9a2f01f80 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -320,7 +320,7 @@ const easeInOutQuad = (currentTime: number, start: number, change: number, durat
return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start;
};
-export default function smoothScroll(duration: number, element: HTMLElement, to: number) {
+export function smoothScroll(duration: number, element: HTMLElement, to: number) {
const start = element.scrollTop;
const change = to - start;
const startDate = new Date().getTime();
@@ -337,4 +337,28 @@ export default function smoothScroll(duration: number, element: HTMLElement, to:
}
};
animateScroll();
+}
+export function addStyleSheet(styleType: string = "text/css") {
+ let style = document.createElement("style");
+ style.type = styleType;
+ var sheets = document.head.appendChild(style);
+ return (sheets as any).sheet;
+}
+export function addStyleSheetRule(sheet: any, selector: any, css: any) {
+ var propText = typeof css === "string" ? css : Object.keys(css).map(p => p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p])).join(";");
+ return sheet.insertRule("." + selector + "{" + propText + "}", sheet.cssRules.length);
+}
+export function removeStyleSheetRule(sheet: any, rule: number) {
+ if (sheet.rules.length) {
+ sheet.removeRule(rule);
+ return true;
+ }
+ return false;
+}
+export function clearStyleSheetRules(sheet: any) {
+ if (sheet.rules.length) {
+ numberRange(sheet.rules.length).map(n => sheet.removeRule(0));
+ return true;
+ }
+ return false;
} \ No newline at end of file
diff --git a/src/client/apis/AuthenticationManager.tsx b/src/client/apis/AuthenticationManager.tsx
new file mode 100644
index 000000000..d8f6b675b
--- /dev/null
+++ b/src/client/apis/AuthenticationManager.tsx
@@ -0,0 +1,90 @@
+import { observable, action, reaction, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import * as React from "react";
+import MainViewModal from "../views/MainViewModal";
+import { Opt } from "../../new_fields/Doc";
+import { Identified } from "../Network";
+
+const AuthenticationUrl = "https://accounts.google.com/o/oauth2/v2/auth";
+const prompt = "Please paste the external authetication code here...";
+
+@observer
+export default class AuthenticationManager extends React.Component<{}> {
+ public static Instance: AuthenticationManager;
+ @observable private openState = false;
+ private authenticationLink: Opt<string> = undefined;
+ @observable private authenticationCode: Opt<string> = undefined;
+ @observable private clickedState = false;
+
+ private set isOpen(value: boolean) {
+ runInAction(() => this.openState = value);
+ }
+
+ private set hasBeenClicked(value: boolean) {
+ runInAction(() => this.clickedState = value);
+ }
+
+ public executeFullRoutine = async (service: string) => {
+ let response = await Identified.FetchFromServer(`/read${service}AccessToken`);
+ // if this is an authentication url, activate the UI to register the new access token
+ if (new RegExp(AuthenticationUrl).test(response)) {
+ this.isOpen = true;
+ this.authenticationLink = response;
+ return new Promise<string>(async resolve => {
+ const disposer = reaction(
+ () => this.authenticationCode,
+ authenticationCode => {
+ if (authenticationCode) {
+ Identified.PostToServer(`/write${service}AccessToken`, { authenticationCode }).then(token => {
+ this.isOpen = false;
+ this.hasBeenClicked = false;
+ resolve(token);
+ disposer();
+ });
+ }
+ }
+ );
+ });
+ }
+ // otherwise, we already have a valid, stored access token
+ return response;
+ }
+
+ constructor(props: {}) {
+ super(props);
+ AuthenticationManager.Instance = this;
+ }
+
+ private handleClick = () => {
+ window.open(this.authenticationLink);
+ this.hasBeenClicked = true;
+ }
+
+ private handlePaste = action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this.authenticationCode = e.currentTarget.value;
+ });
+
+ private get renderPrompt() {
+ return (
+ <div style={{ display: "flex", flexDirection: "column" }}>
+ <button onClick={this.handleClick}>Please click here to authorize a Google account...</button>
+ {this.clickedState ? <input
+ onChange={this.handlePaste}
+ placeholder={prompt}
+ style={{ marginTop: 15 }}
+ /> : (null)}
+ </div>
+ );
+ }
+
+ render() {
+ return (
+ <MainViewModal
+ isDisplayed={this.openState}
+ interactive={true}
+ contents={this.renderPrompt}
+ />
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 29cc042b6..8e88040db 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -13,14 +13,14 @@ import { Docs, DocumentOptions } from "../../documents/Documents";
import { NewMediaItemResult, MediaItem } from "../../../server/apis/google/SharedTypes";
import { AssertionError } from "assert";
import { DocumentView } from "../../views/nodes/DocumentView";
-import { DocumentManager } from "../../util/DocumentManager";
import { Identified } from "../../Network";
+import AuthenticationManager from "../AuthenticationManager";
+import { List } from "../../../new_fields/List";
export namespace GooglePhotos {
const endpoint = async () => {
- const accessToken = await Identified.FetchFromServer(RouteStore.googlePhotosAccessToken);
- return new Photos(accessToken);
+ return new Photos(await AuthenticationManager.Instance.executeFullRoutine("GooglePhotos"));
};
export enum MediaType {
@@ -89,9 +89,14 @@ export namespace GooglePhotos {
}
const resolved = title ? title : (StrCast(collection.title) || `Dash Collection (${collection[Id]}`);
const { id, productUrl } = await Create.Album(resolved);
- const newMediaItemResults = await Transactions.UploadImages(images, { id }, descriptionKey);
- if (newMediaItemResults) {
- const mediaItems = newMediaItemResults.map(item => item.mediaItem);
+ const response = await Transactions.UploadImages(images, { id }, descriptionKey);
+ if (response) {
+ const { results, failed } = response;
+ let index: Opt<number>;
+ while ((index = failed.pop()) !== undefined) {
+ Doc.RemoveDocFromList(dataDocument, "data", images.splice(index, 1)[0]);
+ }
+ const mediaItems: MediaItem[] = results.map(item => item.mediaItem);
if (mediaItems.length !== images.length) {
throw new AssertionError({ actual: mediaItems.length, expected: images.length });
}
@@ -99,6 +104,9 @@ export namespace GooglePhotos {
for (let i = 0; i < images.length; i++) {
const image = Doc.GetProto(images[i]);
const mediaItem = mediaItems[i];
+ if (!mediaItem) {
+ continue;
+ }
image.googlePhotosId = mediaItem.id;
image.googlePhotosAlbumUrl = productUrl;
image.googlePhotosUrl = mediaItem.productUrl || mediaItem.baseUrl;
@@ -304,17 +312,22 @@ export namespace GooglePhotos {
};
export const UploadThenFetch = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption") => {
- const newMediaItems = await UploadImages(sources, album, descriptionKey);
- if (!newMediaItems) {
+ const response = await UploadImages(sources, album, descriptionKey);
+ if (!response) {
return undefined;
}
- const baseUrls: string[] = await Promise.all(newMediaItems.map(item => {
+ const baseUrls: string[] = await Promise.all(response.results.map(item => {
return new Promise<string>(resolve => Query.GetImage(item.mediaItem.id).then(item => resolve(item.baseUrl)));
}));
return baseUrls;
};
- export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption"): Promise<Opt<NewMediaItemResult[]>> => {
+ export interface ImageUploadResults {
+ results: NewMediaItemResult[];
+ failed: number[];
+ }
+
+ export const UploadImages = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption"): Promise<Opt<ImageUploadResults>> => {
if (album && "title" in album) {
album = await Create.Album(album.title);
}
@@ -331,8 +344,8 @@ export namespace GooglePhotos {
media.push({ url, description });
}
if (media.length) {
- const uploads: NewMediaItemResult[] = await Identified.PostToServer(RouteStore.googlePhotosMediaUpload, { media, album });
- return uploads;
+ const results = await Identified.PostToServer(RouteStore.googlePhotosMediaUpload, { media, album });
+ return results;
}
};
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index eed8d61c2..9d1a6ed3e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -73,7 +73,9 @@ export interface DocumentOptions {
dropAction?: dropActionType;
backgroundLayout?: string;
chromeStatus?: string;
+ fontSize?: number;
curPage?: number;
+ currentTimecode?: number;
documentText?: string;
borderRounding?: string;
schemaColumns?: List<SchemaHeaderField>;
@@ -120,7 +122,7 @@ export namespace Docs {
}],
[DocumentType.IMG, {
layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
- options: { curPage: 0 }
+ options: {}
}],
[DocumentType.WEB, {
layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType },
@@ -136,7 +138,7 @@ export namespace Docs {
}],
[DocumentType.VID, {
layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType },
- options: { curPage: 0 },
+ options: { currentTimecode: 0 },
}],
[DocumentType.AUDIO, {
layout: { view: AudioBox },
@@ -591,6 +593,7 @@ export namespace Docs {
if (type.indexOf("pdf") !== -1) {
ctor = Docs.Create.PdfDocument;
options.nativeWidth = 1200;
+ options.nativeHeight = 1200;
}
if (type.indexOf("excel") !== -1) {
ctor = Docs.Create.DBDocument;
@@ -652,32 +655,31 @@ export namespace DocUtils {
}
});
}
- export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string, anchored1?: boolean) {
- let sv = DocumentManager.Instance.getDocumentView(source);
- if (sv && sv.props.ContainingCollectionDoc === target) return;
- if (target === CurrentUserUtils.UserDocument) return undefined;
+ export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) {
+ let sv = DocumentManager.Instance.getDocumentView(source.doc);
+ if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
+ if (target.doc === CurrentUserUtils.UserDocument) return undefined;
let linkDocProto = new Doc(id, true);
UndoManager.RunInBatch(() => {
linkDocProto.type = DocumentType.LINK;
- linkDocProto.targetContext = targetContext;
- linkDocProto.sourceContext = sourceContext;
- linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
+ linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title;
linkDocProto.linkDescription = description;
- linkDocProto.anchor1 = source;
- linkDocProto.anchor1Page = source.curPage;
+ linkDocProto.anchor1 = source.doc;
+ linkDocProto.anchor1Context = source.ctx;
+ linkDocProto.anchor1Timecode = source.doc.currentTimecode;
linkDocProto.anchor1Groups = new List<Doc>([]);
- linkDocProto.anchor1anchored = anchored1;
- linkDocProto.anchor2 = target;
- linkDocProto.anchor2Page = target.curPage;
+ linkDocProto.anchor2 = target.doc;
+ linkDocProto.anchor2Context = target.ctx;
linkDocProto.anchor2Groups = new List<Doc>([]);
+ linkDocProto.anchor2Timecode = target.doc.currentTimecode;
LinkManager.Instance.addLink(linkDocProto);
- Doc.GetProto(source).links = ComputedField.MakeFunction("links(this)");
- Doc.GetProto(target).links = ComputedField.MakeFunction("links(this)");
+ Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)");
+ Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)");
}, "make link");
return linkDocProto;
}
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index cebb56bbe..3b2307073 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -336,7 +336,6 @@ export namespace DictationManager {
let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" });
newBox.autoHeight = true;
let proto = newBox.proto!;
- proto.page = -1;
let prompt = "Press alt + r to start dictating here...";
let head = 3;
let anchor = head + prompt.length;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index c048125c5..c45d3a75f 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, trace } from 'mobx';
import { Doc, DocListCastAsync } from '../../new_fields/Doc';
import { Id } from '../../new_fields/FieldSymbols';
-import { Cast, NumCast } from '../../new_fields/Types';
+import { Cast, NumCast, StrCast } from '../../new_fields/Types';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionPDFView } from '../views/collections/CollectionPDFView';
import { CollectionVideoView } from '../views/collections/CollectionVideoView';
@@ -11,6 +11,8 @@ import { LinkManager } from './LinkManager';
import { undoBatch, UndoManager } from './UndoManager';
import { Scripting } from './Scripting';
import { List } from '../../new_fields/List';
+import { SelectionManager } from './SelectionManager';
+import { notDeepEqual } from 'assert';
export class DocumentManager {
@@ -55,9 +57,9 @@ export class DocumentManager {
return this.getDocumentViewsById(doc[Id]);
}
- public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | null {
+ public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | undefined {
- let toReturn: DocumentView | null = null;
+ let toReturn: DocumentView | undefined;
let passes = preferredCollection ? [preferredCollection, undefined] : [undefined];
for (let pass of passes) {
@@ -80,10 +82,14 @@ export class DocumentManager {
return toReturn;
}
- public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | null {
+ public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionPDFView | CollectionVideoView): DocumentView | undefined {
return this.getDocumentViewById(toFind[Id], preferredCollection);
}
+ public getFirstDocumentView(toFind: Doc): DocumentView | undefined {
+ const views = this.getDocumentViews(toFind);
+ return views.length ? views[0] : undefined;
+ }
public getDocumentViews(toFind: Doc): DocumentView[] {
let toReturn: DocumentView[] = [];
@@ -127,63 +133,80 @@ export class DocumentManager {
}
- @undoBatch
- public jumpToDocument = async (docDelegate: Doc, willZoom: boolean, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void, linkPage?: number, docContext?: Doc): Promise<void> => {
- let doc = Doc.GetProto(docDelegate);
- const contextDoc = await Cast(doc.annotationOn, Doc);
- if (contextDoc) {
- contextDoc.scrollY = NumCast(doc.y) - NumCast(contextDoc.height) / 2;
- }
- let docView: DocumentView | null;
- // using forceDockFunc as a flag for splitting linked to doc to the right...can change later if needed
- if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) {
- Doc.BrushDoc(docView.props.Document);
- if (linkPage !== undefined) docView.props.Document.curPage = linkPage;
- UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus");
+ public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false): Promise<void> => {
+ let highlight = () => {
+ const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
+ finalDocView && (finalDocView.Document.scrollToLinkID = linkId);
+ finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document);
+ };
+ const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
+ const annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
+ if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight?
+ annotatedDoc && docView.props.focus(annotatedDoc, false);
+ docView.props.focus(docView.props.Document, willZoom);
+ highlight();
} else {
- if (!contextDoc) {
- let docs = docContext ? await DocListCastAsync(docContext.data) : undefined;
- let found = false;
- // bcz: this just searches within the context for the target -- perhaps it should recursively search through all children?
- docs && docs.map(d => found = found || Doc.AreProtosEqual(d, docDelegate));
- if (docContext && found) {
- let targetContextView: DocumentView | null;
-
- if (!forceDockFunc && docContext && (targetContextView = DocumentManager.Instance.getDocumentView(docContext))) {
- docContext.panTransformType = "Ease";
- targetContextView.props.focus(docDelegate, willZoom);
- } else {
- (dockFunc || CollectionDockingView.AddRightSplit)(docContext, undefined);
- setTimeout(() => {
- let dv = DocumentManager.Instance.getDocumentView(docContext);
- dv && this.jumpToDocument(docDelegate, willZoom, forceDockFunc,
- doc => dv!.props.focus(dv!.props.Document, true, 1, () => dv!.props.addDocTab(doc, undefined, "inPlace")),
- linkPage);
- }, 1050);
- }
- } else {
- const actualDoc = Doc.MakeAlias(docDelegate);
- Doc.BrushDoc(actualDoc);
- if (linkPage !== undefined) actualDoc.curPage = linkPage;
- (dockFunc || CollectionDockingView.AddRightSplit)(actualDoc, undefined);
- }
+ const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
+ const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
+ const targetDocContext = (annotatedDoc ? annotatedDoc : contextDoc);
+
+ if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default
+ (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined);
+ highlight();
} else {
- let contextView: DocumentView | null;
- Doc.BrushDoc(docDelegate);
- if (!forceDockFunc && (contextView = DocumentManager.Instance.getDocumentView(contextDoc))) {
- contextDoc.panTransformType = "Ease";
- contextView.props.focus(docDelegate, willZoom);
- } else {
- (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc, undefined);
+ const targetDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext);
+ targetDocContext.scrollY = 0; // this will force PDFs to activate and load their annotations / allow scrolling
+ if (targetDocContextView) { // we have a context view and aren't forced to create a new one ... focus on the context
+ targetDocContext.panTransformType = "Ease";
+ targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom);
+
+ // now find the target document within the context
+ setTimeout(() => {
+ const retryDocView = DocumentManager.Instance.getDocumentView(targetDoc);
+ if (retryDocView) {
+ retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context
+ } else {
+ if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document);
+ targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target
+ }
+ highlight();
+ }, 0);
+ } else { // there's no context view so we need to create one first and try again
+ (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext, undefined);
setTimeout(() => {
- this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
- }, 1000);
+ const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc);
+ const finalDocContextView = DocumentManager.Instance.getFirstDocumentView(targetDocContext);
+ setTimeout(() => // if not, wait a bit to see if the context can be loaded (e.g., a PDF). wait interval heurisitic tries to guess how we're animating based on what's just become visible
+ this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true), finalDocView ? 0 : finalDocContextView ? 250 : 2000); // so call jump to doc again and if the doc isn't found, it will be created.
+ }, 0);
}
}
}
}
+ public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) {
+ const linkDocs = link ? [link] : LinkManager.Instance.getAllRelatedLinks(doc);
+ SelectionManager.DeselectAll();
+ const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc));
+ const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc));
+ const firstDocWithoutView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
+ const secondDocWithoutView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
+ const first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs;
+ const second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs;
+ const linkDoc = first.length ? first[0] : second.length ? second[0] : undefined;
+ const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined;
+ const linkFollowDocContexts = first.length ? [await first[0].anchor2Context as Doc, await first[0].anchor1Context as Doc] : second.length ? [await second[0].anchor1Context as Doc, await second[0].anchor2Context as Doc] : [undefined, undefined];
+ const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2Timecode), NumCast(first[0].anchor1Timecode)] : second.length ? [NumCast(second[0].anchor1Timecode), NumCast(second[0].anchor2Timecode)] : [undefined, undefined];
+ if (linkFollowDocs && linkDoc) {
+ const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab");
+ const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined;
+ const target = linkFollowDocs[reverse ? 1 : 0];
+ target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]);
+ DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]);
+ }
+ }
+
@action
zoomIntoScale = (docDelegate: Doc, scale: number) => {
let docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
@@ -193,8 +216,7 @@ export class DocumentManager {
getScaleOfDocView = (docDelegate: Doc) => {
let doc = Doc.GetProto(docDelegate);
- let docView: DocumentView | null;
- docView = DocumentManager.Instance.getDocumentView(doc);
+ const docView = DocumentManager.Instance.getDocumentView(doc);
if (docView) {
return docView.props.getScale();
} else {
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index dd0f72af0..003ff6272 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -11,7 +11,7 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) :
export type KeyMap = { [key: string]: any };
-export let updateBullets = (tx2: Transaction, schema: Schema) => {
+export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => {
let fontSize: number | undefined = undefined;
tx2.doc.descendants((node: any, offset: any, index: any) => {
if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
@@ -20,7 +20,7 @@ export let updateBullets = (tx2: Transaction, schema: Schema) => {
if (node.type === schema.nodes.ordered_list) depth++;
fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
let fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
- tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
+ tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
}
});
return tx2;
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index cd37ea0bb..ebb9bda8a 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -1,10 +1,12 @@
import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules";
import { schema } from "./RichTextSchema";
import { wrappingInputRule } from "./prosemirrorPatches";
-import { NodeSelection } from "prosemirror-state";
+import { NodeSelection, TextSelection } from "prosemirror-state";
import { NumCast, Cast } from "../../new_fields/Types";
import { Doc } from "../../new_fields/Doc";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { Docs } from "../documents/Documents";
+import { Id } from "../../new_fields/FieldSymbols";
export const inpRules = {
rules: [
@@ -70,6 +72,34 @@ export const inpRules = {
return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }));
}),
new InputRule(
+ new RegExp(/t/),
+ (state, match, start, end) => {
+ if (state.selection.to === state.selection.from) return null;
+ let node = (state.doc.resolve(start) as any).nodeAfter;
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
+ new InputRule(
+ new RegExp(/i/),
+ (state, match, start, end) => {
+ if (state.selection.to === state.selection.from) return null;
+ let node = (state.doc.resolve(start) as any).nodeAfter;
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
+ new InputRule(
+ new RegExp(/\!/),
+ (state, match, start, end) => {
+ if (state.selection.to === state.selection.from) return null;
+ let node = (state.doc.resolve(start) as any).nodeAfter;
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
+ new InputRule(
+ new RegExp(/\x/),
+ (state, match, start, end) => {
+ if (state.selection.to === state.selection.from) return null;
+ let node = (state.doc.resolve(start) as any).nodeAfter;
+ return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr;
+ }),
+ new InputRule(
new RegExp(/^\^\^\s$/),
(state, match, start, end) => {
let node = (state.doc.resolve(start) as any).nodeAfter;
@@ -80,8 +110,9 @@ export const inpRules = {
ruleProvider["ruleAlign_" + heading] = "center";
return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}
- return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
new InputRule(
new RegExp(/^\[\[\s$/),
@@ -92,8 +123,11 @@ export const inpRules = {
let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "left";
+ return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}
- return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
}),
new InputRule(
new RegExp(/^\]\]\s$/),
@@ -104,8 +138,44 @@ export const inpRules = {
let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "right";
+ return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}
- return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
+ let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2)));
+ }),
+ new InputRule(
+ new RegExp(/##\s$/),
+ (state, match, start, end) => {
+ let node = (state.doc.resolve(start) as any).nodeAfter;
+ let sm = state.storedMarks || undefined;
+ let target = Docs.Create.TextDocument({ width: 75, height: 35, autoHeight: true, fontSize: 9, title: "inline comment" });
+ let replaced = node ? state.tr.insertText("←", start).replaceRangeWith(start + 1, end + 1, schema.nodes.dashDoc.create({
+ width: 75, height: 35,
+ title: "dashDoc", docid: target[Id],
+ float: "right"
+ })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 1)));
+ }),
+ new InputRule(
+ new RegExp(/\(\(/),
+ (state, match, start, end) => {
+ let node = (state.doc.resolve(start) as any).nodeAfter;
+ let sm = state.storedMarks || undefined;
+ let mark = state.schema.marks.highlight.create();
+ let selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark);
+ let content = selected.selection.content();
+ let replaced = node ? selected.replaceRangeWith(start, start,
+ schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
+ state.tr;
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1)));
+ }),
+ new InputRule(
+ new RegExp(/\)\)/),
+ (state, match, start, end) => {
+ let mark = state.schema.marks.highlight.create();
+ return state.tr.removeStoredMark(mark);
}),
new InputRule(
new RegExp(/\^f\s$/),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 64821d8db..80bcf25ad 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,18 +1,23 @@
+import { action, observable, runInAction, reaction, IReactionDisposer } from "mobx";
import { baseKeymap, toggleMark } from "prosemirror-commands";
import { redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { EditorState, TextSelection } from "prosemirror-state";
+import { EditorState, NodeSelection, TextSelection, Plugin } from "prosemirror-state";
import { StepMap } from "prosemirror-transform";
import { EditorView } from "prosemirror-view";
-import { Doc } from "../../new_fields/Doc";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import * as ReactDOM from 'react-dom';
+import { Doc, WidthSym, HeightSym } from "../../new_fields/Doc";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
-import { Cast, NumCast } from "../../new_fields/Types";
+import { DocumentView } from "../views/nodes/DocumentView";
import { DocumentManager } from "./DocumentManager";
import ParagraphNodeSpec from "./ParagraphNodeSpec";
-import { times } from "async";
+import { Transform } from "./Transform";
+import React = require("react");
+import { BoolCast, NumCast } from "../../new_fields/Types";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -157,6 +162,35 @@ export const nodes: { [index: string]: NodeSpec } = {
}
},
+ dashDoc: {
+ inline: true,
+ attrs: {
+ width: { default: 200 },
+ height: { default: 100 },
+ title: { default: null },
+ float: { default: "right" },
+ location: { default: "onRight" },
+ docid: { default: "" }
+ },
+ group: "inline",
+ draggable: true,
+ // parseDOM: [{
+ // tag: "img[src]", getAttrs(dom: any) {
+ // return {
+ // src: dom.getAttribute("src"),
+ // title: dom.getAttribute("title"),
+ // alt: dom.getAttribute("alt"),
+ // width: Math.min(100, Number(dom.getAttribute("width"))),
+ // };
+ // }
+ // }],
+ // TODO if we don't define toDom, dragging the image crashes. Why?
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+ return ["div", { ...node.attrs, ...attrs }];
+ }
+ },
+
video: {
inline: true,
attrs: {
@@ -200,6 +234,7 @@ export const nodes: { [index: string]: NodeSpec } = {
bulletStyle: { default: 0 },
mapStyle: { default: "decimal" },
setFontSize: { default: undefined },
+ setFontFamily: { default: undefined },
inheritedFontSize: { default: undefined },
visibility: { default: true }
},
@@ -209,8 +244,9 @@ export const nodes: { [index: string]: NodeSpec } = {
const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
let fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
- return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;font-size: ${fsize}` }, 0] :
- ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}` }];
+ let ffam = node.attrs.setFontFamily;
+ return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }, 0] :
+ ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }];
}
},
@@ -403,20 +439,34 @@ export const marks: { [index: string]: MarkSpec } = {
user_mark: {
attrs: {
userid: { default: "" },
- hide_users: { default: [] },
opened: { default: true },
- modified: { default: "when?" }
+ modified: { default: "when?" }, // 5 second intervals since 1970
},
group: "inline",
toDOM(node: any) {
- let hideUsers = node.attrs.hide_users;
- let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail);
- return hidden ?
- (node.attrs.opened ?
- ['span', { class: "userMarkOpen" }, 0] :
- ['span', { class: "userMark" }, ['span', 0]]
- ) :
- ['span', 0];
+ let uid = node.attrs.userid.replace(".", "").replace("@", "");
+ let min = Math.round(node.attrs.modified / 12);
+ let hr = Math.round(min / 60);
+ let day = Math.round(hr / 60 / 24);
+ return node.attrs.opened ?
+ ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] :
+ ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]];
+ }
+ },
+ // the id of the user who entered the text
+ user_tag: {
+ attrs: {
+ userid: { default: "" },
+ opened: { default: true },
+ modified: { default: "when?" }, // 5 second intervals since 1970
+ tag: { default: "" }
+ },
+ group: "inline",
+ toDOM(node: any) {
+ let uid = node.attrs.userid.replace(".", "").replace("@", "");
+ return node.attrs.opened ?
+ ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0] :
+ ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, ['span', 0]];
}
},
@@ -600,6 +650,7 @@ export class ImageResizeView {
this._outer = document.createElement("span");
this._outer.style.position = "relative";
this._outer.style.width = node.attrs.width;
+ this._outer.style.height = node.attrs.height;
this._outer.style.display = "inline-block";
this._outer.style.overflow = "hidden";
(this._outer.style as any).float = node.attrs.float;
@@ -615,63 +666,51 @@ export class ImageResizeView {
this._handle.style.bottom = "-10px";
this._handle.style.right = "-10px";
let self = this;
+ this._img.onclick = function (e: any) {
+ e.stopPropagation();
+ e.preventDefault();
+ if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) {
+ view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2))));
+ }
+ };
this._img.onpointerdown = function (e: any) {
- if (!view.isOverlay || e.ctrlKey) {
+ if (e.ctrlKey) {
e.preventDefault();
e.stopPropagation();
- DocServer.GetRefField(node.attrs.docid).then(async linkDoc => {
- const location = node.attrs.location;
- if (linkDoc instanceof Doc) {
- let proto = Doc.GetProto(linkDoc);
- let targetContext = await Cast(proto.targetContext, Doc);
- let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
- if (jumpToDoc) {
- if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
- return;
- }
- }
- if (targetContext) {
- DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
- } else if (jumpToDoc) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
- } else {
- DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
- }
- }
- });
+ DocServer.GetRefField(node.attrs.docid).then(async linkDoc =>
+ (linkDoc instanceof Doc) &&
+ DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document,
+ document => addDocTab(document, undefined, node.attrs.location ? node.attrs.location : "inTab"), false));
}
};
this._handle.onpointerdown = function (e: any) {
e.preventDefault();
e.stopPropagation();
+ let wid = Number(getComputedStyle(self._img).width!.replace(/px/, ""));
+ let hgt = Number(getComputedStyle(self._img).height!.replace(/px/, ""));
const startX = e.pageX;
const startWidth = parseFloat(node.attrs.width);
const onpointermove = (e: any) => {
const currentX = e.pageX;
const diffInPx = currentX - startX;
self._outer.style.width = `${startWidth + diffInPx}`;
- //Array.from(FormattedTextBox.InputBoxOverlay!.CurrentDiv.getElementsByTagName("img")).map((img: any) => img.opacity = "0.1");
- FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "0";
+ self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`;
};
const onpointerup = () => {
document.removeEventListener("pointermove", onpointermove);
document.removeEventListener("pointerup", onpointerup);
- view.dispatch(
- view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null,
- { ...node.attrs, width: self._outer.style.width })
- );
- FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1";
+ let pos = view.state.selection.from;
+ view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height }));
+ view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos))));
};
document.addEventListener("pointermove", onpointermove);
document.addEventListener("pointerup", onpointerup);
};
- this._outer.appendChild(this._handle);
this._outer.appendChild(this._img);
+ this._outer.appendChild(this._handle);
(this as any).dom = this._outer;
}
@@ -688,6 +727,88 @@ export class ImageResizeView {
}
}
+export class DashDocView {
+ _dashSpan: HTMLDivElement;
+ _outer: HTMLElement;
+ _dashDoc: Doc | undefined;
+ _reactionDisposer: IReactionDisposer | undefined;
+ _textBox: FormattedTextBox;
+
+ getDocTransform = () => {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer);
+ return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale);
+ }
+ contentScaling = () => NumCast(this._dashDoc!.nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1;
+ constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
+ this._textBox = tbox;
+ this._dashSpan = document.createElement("div");
+ this._outer = document.createElement("span");
+ this._outer.style.position = "relative";
+ this._outer.style.width = node.attrs.width;
+ this._outer.style.height = node.attrs.height;
+ this._outer.style.display = "inline-block";
+ this._outer.style.overflow = "hidden";
+ (this._outer.style as any).float = node.attrs.float;
+
+ this._dashSpan.style.width = node.attrs.width;
+ this._dashSpan.style.height = node.attrs.height;
+ this._dashSpan.style.position = "absolute";
+ this._dashSpan.style.display = "inline-block";
+ let removeDoc = () => {
+ let pos = getPos();
+ let ns = new NodeSelection(view.state.doc.resolve(pos));
+ view.dispatch(view.state.tr.setSelection(ns).deleteSelection());
+ return true;
+ };
+ DocServer.GetRefField(node.attrs.docid).then(async dashDoc => {
+ if (dashDoc instanceof Doc) {
+ self._dashDoc = dashDoc;
+ if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") {
+ view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" }));
+ }
+ this._reactionDisposer && this._reactionDisposer();
+ this._reactionDisposer = reaction(() => dashDoc[HeightSym]() + dashDoc[WidthSym](), () => {
+ this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px";
+ this._dashSpan.style.width = this._outer.style.width = dashDoc[WidthSym]() + "px";
+ });
+ ReactDOM.render(<DocumentView
+ fitToBox={BoolCast(dashDoc.fitToBox)}
+ Document={dashDoc}
+ addDocument={returnFalse}
+ removeDocument={removeDoc}
+ ruleProvider={undefined}
+ ScreenToLocalTransform={this.getDocTransform}
+ addDocTab={self._textBox.props.addDocTab}
+ pinToPres={returnFalse}
+ renderDepth={1}
+ PanelWidth={self._dashDoc![WidthSym]}
+ PanelHeight={self._dashDoc![HeightSym]}
+ focus={emptyFunction}
+ backgroundColor={returnEmptyString}
+ parentActive={returnFalse}
+ whenActiveChanged={returnFalse}
+ bringToFront={emptyFunction}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ ContentScaling={this.contentScaling}
+ />, this._dashSpan);
+ }
+ });
+ let self = this;
+ this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); };
+ this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); };
+ this._dashSpan.onwheel = function (e: any) { e.preventDefault(); };
+ this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); };
+ this._outer.appendChild(this._dashSpan);
+ (this as any).dom = this._outer;
+ }
+ destroy() {
+ this._reactionDisposer && this._reactionDisposer();
+ }
+}
+
export class OrderedListView {
update(node: any) {
return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
@@ -727,7 +848,6 @@ export class FootnoteView {
if (this.innerView) this.close();
}
open() {
- if (!this.outerView.isOverlay) return;
// Append a tooltip to the outer node
let tooltip = this.dom.appendChild(document.createElement("div"));
tooltip.className = "footnote-tooltip";
@@ -741,7 +861,14 @@ export class FootnoteView {
"Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
"Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
"Mod-b": toggleMark(schema.marks.strong)
- })]
+ }),
+ new Plugin({
+ view(newView) {
+ return FormattedTextBox.getToolTip(newView);
+ }
+ })
+ ],
+
}),
// This is the magic part
dispatchTransaction: this.dispatchInner.bind(this),
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index d8b9dbec6..6706dcb89 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -3,7 +3,6 @@ import { DocServer } from '../DocServer';
import { Doc } from '../../new_fields/Doc';
import { Id } from '../../new_fields/FieldSymbols';
import { Utils } from '../../Utils';
-import { ResultParameters } from '../northstar/model/idea/idea';
import { DocumentType } from '../documents/DocumentTypes';
export namespace SearchUtil {
@@ -42,23 +41,45 @@ export namespace SearchUtil {
}
let { ids, numFound, highlighting } = result;
- let lines: string[][] = ids.map(i => []);
let txtresult = query !== "*" && JSON.parse(await rp.get(Utils.prepend("/textsearch"), {
qs: { ...options, q: query },
}));
+
let fileids = txtresult ? txtresult.ids : [];
+ let newIds: string[] = [];
+ let newLines: string[][] = [];
await Promise.all(fileids.map(async (tr: string, i: number) => {
let docQuery = "fileUpload_t:" + tr.substr(0, 7); //If we just have a filter query, search for * as the query
let docResult = JSON.parse(await rp.get(Utils.prepend("/search"), { qs: { ...options, q: docQuery } }));
- ids.push(...docResult.ids);
- lines.push(...docResult.ids.map((dr: any) => txtresult.lines[i]));
- numFound += docResult.numFound;
+ newIds.push(...docResult.ids);
+ newLines.push(...docResult.ids.map((dr: any) => txtresult.lines[i]));
}));
+
+ let theDocs: Doc[] = [];
+ let theLines: string[][] = [];
+ const textDocMap = await DocServer.GetRefFields(newIds);
+ const textDocs = newIds.map((id: string) => textDocMap[id]).map(doc => doc as Doc);
+ for (let i = 0; i < textDocs.length; i++) {
+ let testDoc = textDocs[i];
+ if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) {
+ theDocs.push(Doc.GetProto(testDoc));
+ theLines.push(newLines[i].map(line => line.replace(query, query.toUpperCase())));
+ }
+ }
+
const docMap = await DocServer.GetRefFields(ids);
- const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc && doc.type !== DocumentType.KVP);
- return { docs, numFound, highlighting, lines };
+ const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc);
+ for (let i = 0; i < ids.length; i++) {
+ let testDoc = docs[i];
+ if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) {
+ theDocs.push(testDoc);
+ theLines.push([]);
+ }
+ }
+
+ return { docs: theDocs, numFound: theDocs.length, highlighting, lines: theLines };
}
export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]>;
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index a02a270ee..df1b46b33 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -27,7 +27,6 @@ export namespace SelectionManager {
} else if (!ctrlPressed && manager.SelectedDocuments.length > 1) {
manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false));
manager.SelectedDocuments = [docView];
- FormattedTextBox.InputBoxOverlay = undefined;
}
}
@action
@@ -42,7 +41,6 @@ export namespace SelectionManager {
DeselectAll(): void {
manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
manager.SelectedDocuments = [];
- FormattedTextBox.InputBoxOverlay = undefined;
}
}
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
index 9a4c5db30..dec9f751a 100644
--- a/src/client/util/SharingManager.scss
+++ b/src/client/util/SharingManager.scss
@@ -2,6 +2,10 @@
display: flex;
flex-direction: column;
+ .focus-span {
+ text-decoration: underline;
+ }
+
p {
font-size: 20px;
text-align: left;
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index f427e40b1..91c8c572d 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,12 +1,9 @@
-import { observable, runInAction, action, autorun } from "mobx";
+import { observable, runInAction, action } from "mobx";
import * as React from "react";
import MainViewModal from "../views/MainViewModal";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
-import { Doc, Opt } from "../../new_fields/Doc";
+import { Doc, Opt, DocCastAsync } from "../../new_fields/Doc";
import { DocServer } from "../DocServer";
import { Cast, StrCast } from "../../new_fields/Types";
-import { listSpec } from "../../new_fields/Schema";
-import { List } from "../../new_fields/List";
import { RouteStore } from "../../server/RouteStore";
import * as RequestPromise from "request-promise";
import { Utils } from "../../Utils";
@@ -49,11 +46,18 @@ const SharingKey = "sharingPermissions";
const PublicKey = "publicLinkPermissions";
const DefaultColor = "black";
+interface ValidatedUser {
+ user: User;
+ notificationDoc: Doc;
+}
+
+const storage = "data";
+
@observer
export default class SharingManager extends React.Component<{}> {
public static Instance: SharingManager;
@observable private isOpen = false;
- @observable private users: User[] = [];
+ @observable private users: ValidatedUser[] = [];
@observable private targetDoc: Doc | undefined;
@observable private targetDocView: DocumentView | undefined;
@observable private copied = false;
@@ -79,6 +83,7 @@ export default class SharingManager extends React.Component<{}> {
public close = action(() => {
this.isOpen = false;
+ this.users = [];
setTimeout(action(() => {
this.copied = false;
MainView.Instance.hasActiveModal = false;
@@ -101,53 +106,43 @@ export default class SharingManager extends React.Component<{}> {
populateUsers = async () => {
let userList = await RequestPromise.get(Utils.prepend(RouteStore.getUsers));
- runInAction(() => {
- this.users = (JSON.parse(userList) as User[]).filter(({ email }) => email !== Doc.CurrentUserEmail);
+ const raw = JSON.parse(userList) as User[];
+ const evaluating = raw.map(async user => {
+ let isCandidate = user.email !== Doc.CurrentUserEmail;
+ if (isCandidate) {
+ const userDocument = await DocServer.GetRefField(user.userDocumentId);
+ if (userDocument instanceof Doc) {
+ const notificationDoc = await Cast(userDocument.optionalRightCollection, Doc);
+ runInAction(() => {
+ if (notificationDoc instanceof Doc) {
+ this.users.push({ user, notificationDoc });
+ }
+ });
+ }
+ }
});
+ return Promise.all(evaluating);
}
- setInternalSharing = async (user: User, state: string) => {
- if (!this.sharingDoc) {
- console.log("SHARING ABORTED!");
- return;
- }
- let sharingDoc = await this.sharingDoc;
- sharingDoc[user.userDocumentId] = state;
- const userDocument = await DocServer.GetRefField(user.userDocumentId);
- if (!(userDocument instanceof Doc)) {
- console.log(`Couldn't get user document of user ${user.email}`);
- return;
- }
- let target = this.targetDoc;
- if (!target) {
- console.log("SharingManager trying to share an undefined document!!");
- return;
- }
- const notifDoc = await Cast(userDocument.optionalRightCollection, Doc);
- if (notifDoc instanceof Doc) {
- const data = await Cast(notifDoc.data, listSpec(Doc));
- if (!data) {
- console.log("UNABLE TO ACCESS NOTIFICATION DATA");
- return;
- }
- console.log(`Attempting to set permissions to ${state} for the document ${target[Id]}`);
- if (state !== SharingPermissions.None) {
- const sharedDoc = Doc.MakeAlias(target);
- if (data) {
- data.push(sharedDoc);
- } else {
- notifDoc.data = new List([sharedDoc]);
- }
- } else {
- let dataDocs = (await Promise.all(data.map(doc => doc))).map(doc => Doc.GetProto(doc));
- if (dataDocs.includes(target)) {
- console.log("Searching in ", dataDocs, "for", target);
- dataDocs.splice(dataDocs.indexOf(target), 1);
- console.log("SUCCESSFULLY UNSHARED DOC");
- } else {
- console.log("DIDN'T THINK WE HAD IT, SO NOT SUCCESSFULLY UNSHARED");
- }
+ setInternalSharing = async (recipient: ValidatedUser, state: string) => {
+ const { user, notificationDoc } = recipient;
+ const target = this.targetDoc!;
+ const manager = this.sharingDoc!;
+ const key = user.userDocumentId;
+ if (state === SharingPermissions.None) {
+ const metadata = (await DocCastAsync(manager[key]));
+ if (metadata) {
+ let sharedAlias = (await DocCastAsync(metadata.sharedAlias))!;
+ Doc.RemoveDocFromList(notificationDoc, storage, sharedAlias);
+ manager[key] = undefined;
}
+ } else {
+ const sharedAlias = Doc.MakeAlias(target);
+ Doc.AddDocToList(notificationDoc, storage, sharedAlias);
+ const metadata = new Doc;
+ metadata.permissions = state;
+ metadata.sharedAlias = sharedAlias;
+ manager[key] = metadata;
}
}
@@ -188,11 +183,12 @@ export default class SharingManager extends React.Component<{}> {
let title = this.targetDoc ? StrCast(this.targetDoc.title) : "";
return (
<span
+ className={"focus-span"}
title={title}
onClick={() => {
let context: Opt<CollectionVideoView | CollectionPDFView | CollectionView>;
if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) {
- DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, undefined, undefined, context.props.Document);
+ DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document);
}
}}
onPointerEnter={action(() => {
@@ -213,7 +209,20 @@ export default class SharingManager extends React.Component<{}> {
);
}
+ private computePermissions = (userKey: string) => {
+ const sharingDoc = this.sharingDoc;
+ if (!sharingDoc) {
+ return SharingPermissions.None;
+ }
+ const metadata = sharingDoc[userKey] as Doc;
+ if (!metadata) {
+ return SharingPermissions.None;
+ }
+ return StrCast(metadata.permissions, SharingPermissions.None)!;
+ }
+
private get sharingInterface() {
+ const existOtherUsers = this.users.length > 0;
return (
<div className={"sharing-interface"}>
<p className={"share-link"}>Manage the public link to {this.focusOn("this document...")}</p>
@@ -247,25 +256,24 @@ export default class SharingManager extends React.Component<{}> {
</div>
<div className={"hr-substitute"} />
<p className={"share-individual"}>Privately share {this.focusOn("this document")} with an individual...</p>
- <div className={"users-list"} style={{ display: this.users.length ? "block" : "flex" }}>
- {!this.users.length ? "There are no other users in your database." :
- this.users.map(user => {
+ <div className={"users-list"} style={{ display: existOtherUsers ? "block" : "flex", minHeight: existOtherUsers ? undefined : 200 }}>
+ {!existOtherUsers ? "There are no other users in your database." :
+ this.users.map(({ user, notificationDoc }) => {
+ const userKey = user.userDocumentId;
+ const permissions = this.computePermissions(userKey);
+ const color = ColorMapping.get(permissions);
return (
<div
- key={user.email}
+ key={userKey}
className={"container"}
>
<select
className={"permissions-dropdown"}
- value={this.sharingDoc ? StrCast(this.sharingDoc[user.userDocumentId], SharingPermissions.None) : SharingPermissions.None}
- style={{
- color: this.sharingDoc ? ColorMapping.get(StrCast(this.sharingDoc[user.userDocumentId], SharingPermissions.None)) : DefaultColor,
- borderColor: this.sharingDoc ? ColorMapping.get(StrCast(this.sharingDoc[user.userDocumentId], SharingPermissions.None)) : DefaultColor
- }}
- onChange={e => this.setInternalSharing(user, e.currentTarget.value)}
+ value={permissions}
+ style={{ color, borderColor: color }}
+ onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)}
>
{this.sharingOptions}
-
</select>
<span className={"padding"}>{user.email}</span>
</div>
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a83a3949d..c82d3bc63 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -10,7 +10,6 @@ import { Doc, Field, Opt } from "../../new_fields/Doc";
import { Id } from "../../new_fields/FieldSymbols";
import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { FieldViewProps } from "../views/nodes/FieldView";
import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
import { DocumentManager } from "./DocumentManager";
@@ -20,6 +19,7 @@ import { schema } from "./RichTextSchema";
import "./TooltipTextMenu.scss";
import { Cast, NumCast } from '../../new_fields/Types';
import { updateBullets } from './ProsemirrorExampleTransfer';
+import { DocumentDecorations } from '../views/DocumentDecorations';
const { toggleMark, setBlockType } = require("prosemirror-commands");
const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
@@ -28,10 +28,10 @@ export class TooltipTextMenu {
public tooltip: HTMLElement;
private view: EditorView;
+ private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
private fontStyles: MarkType[];
private fontSizes: MarkType[];
private listTypes: (NodeType | any)[];
- private editorProps: FieldViewProps & FormattedTextBoxProps;
private fontSizeToNum: Map<MarkType, number>;
private fontStylesToName: Map<MarkType, string>;
private listTypeToIcon: Map<NodeType | any, string>;
@@ -59,9 +59,8 @@ export class TooltipTextMenu {
private _collapsed: boolean = false;
- constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
+ constructor(view: EditorView) {
this.view = view;
- this.editorProps = editorProps;
this.wrapper = document.createElement("div");
this.tooltip = document.createElement("div");
@@ -120,10 +119,10 @@ export class TooltipTextMenu {
//pointer down handler to activate button effects
dom.addEventListener("pointerdown", e => {
e.preventDefault();
- view.focus();
+ this.view.focus();
if (dom.contains(e.target as Node)) {
e.stopPropagation();
- command(view.state, view.dispatch, view);
+ command(this.view.state, this.view.dispatch, this.view);
// if (this.view.state.selection.empty) {
// if (dom.style.color === "white") { dom.style.color = "greenyellow"; }
// else { dom.style.color = "white"; }
@@ -188,12 +187,10 @@ export class TooltipTextMenu {
this.updateListItemDropdown(":", this.listTypeBtnDom);
- this.update(view, undefined);
-
- // add tooltip to outerdiv to circumvent scaling problem
- const outer_div = this.editorProps.outer_div;
- outer_div && outer_div(this.wrapper);
+ this.updateFromDash(view, undefined, undefined);
+ TooltipTextMenu.Toolbar = this.wrapper;
}
+ public static Toolbar: HTMLDivElement | undefined;
//label of dropdown will change to given label
updateFontSizeDropdown(label: string) {
@@ -275,7 +272,7 @@ export class TooltipTextMenu {
if (DocumentManager.Instance.getDocumentView(f)) {
DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false);
}
- else this.editorProps.addDocTab(f, undefined, "onRight");
+ else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight");
}
}));
}
@@ -293,6 +290,7 @@ export class TooltipTextMenu {
this.linkDrag.style.background = "black";
this.linkDrag.style.cssFloat = "left";
this.linkDrag.onpointerdown = (e: PointerEvent) => {
+ if (!this.editorProps) return;
let dragData = new DragManager.LinkDragData(this.editorProps.Document);
dragData.dontClearTextBox = true;
// hack to get source context -sy
@@ -442,7 +440,7 @@ export class TooltipTextMenu {
let tr = state.tr;
tr.addMark(state.selection.from, state.selection.to, mark);
let content = tr.selection.content();
- let newNode = schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+ let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
return true;
}
@@ -503,40 +501,35 @@ export class TooltipTextMenu {
if (markType.name[0] === 'p') {
let size = this.fontSizeToNum.get(markType);
if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
- let ruleProvider = this.editorProps.ruleProvider;
- let heading = NumCast(this.editorProps.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleSize_" + heading] = size;
+ if (this.editorProps) {
+ let ruleProvider = this.editorProps.ruleProvider;
+ let heading = NumCast(this.editorProps.Document.heading);
+ if (ruleProvider && heading) {
+ ruleProvider["ruleSize_" + heading] = size;
+ }
}
}
else {
let fontName = this.fontStylesToName.get(markType);
if (fontName) { this.updateFontStyleDropdown(fontName); }
- let ruleProvider = this.editorProps.ruleProvider;
- let heading = NumCast(this.editorProps.Document.heading);
- if (ruleProvider && heading) {
- ruleProvider["ruleFont_" + heading] = fontName;
+ if (this.editorProps) {
+ let ruleProvider = this.editorProps.ruleProvider;
+ let heading = NumCast(this.editorProps.Document.heading);
+ if (ruleProvider && heading) {
+ ruleProvider["ruleFont_" + heading] = fontName;
+ }
}
}
//actually apply font
if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
- view.dispatch(updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
- { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema));
+ let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
+ { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema);
+ view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from))));
}
else toggleMark(markType)(view.state, view.dispatch, view);
}
}
- updateBullets = (tx2: Transaction, style: string) => {
- tx2.doc.descendants((node: any, offset: any, index: any) => {
- if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
- let path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
- if (node.type === schema.nodes.ordered_list) depth++;
- tx2.setNodeMarkup(offset, node.type, { mapStyle: style, bulletStyle: depth }, node.marks);
- }
- });
- }
//remove all node typeand apply the passed-in one to the selected text
changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => {
//remove oldif (nodeType) { //add new
@@ -545,18 +538,18 @@ export class TooltipTextMenu {
} else {
var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
- this.updateBullets(tx2, (nodeType as any).attrs.mapStyle);
- marks && tx2.ensureMarks([...marks]);
- marks && tx2.setStoredMarks([...marks]);
+ let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
view.dispatch(tx2);
})) {
let tx2 = view.state.tr;
- this.updateBullets(tx2, (nodeType as any).attrs.mapStyle);
- marks && tx2.ensureMarks([...marks]);
- marks && tx2.setStoredMarks([...marks]);
+ let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2;
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
- view.dispatch(tx2);
+ view.dispatch(tx3);
}
}
}
@@ -586,7 +579,7 @@ export class TooltipTextMenu {
class: "summarize",
execEvent: "",
run: (state, dispatch) => {
- TooltipTextMenu.insertStar(state, dispatch);
+ TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
}
});
@@ -848,9 +841,17 @@ export class TooltipTextMenu {
}
}
+ update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); }
//updates the tooltip menu when the selection changes
- update(view: EditorView, lastState: EditorState | undefined) {
+ public updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
+ if (!view) {
+ console.log("no editor? why?");
+ return;
+ }
+ this.view = view;
let state = view.state;
+ DocumentDecorations.Instance.TextBar && DocumentDecorations.Instance.setTextBar(DocumentDecorations.Instance.TextBar);
+ props && (this.editorProps = props);
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) &&
lastState.selection.eq(state.selection)) return;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 338f6b83e..9e2d41621 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -140,7 +140,6 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
let selDoc = this.props.views[0];
let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined;
let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
- FormattedTextBox.InputBoxOverlay = undefined;
this._linkDrag = UndoManager.StartBatch("Drag Link");
DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
handlers: {
@@ -203,7 +202,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
considerEmbed = () => {
let thisDoc = this.props.views[0].props.Document;
let canEmbed = thisDoc.data && thisDoc.data instanceof URLField;
- if (!canEmbed) return (null);
+ // if (!canEmbed) return (null);
return (
<div className="linkButtonWrapper">
<div title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 9d42eb719..4f9bdbe9c 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,6 +24,7 @@ import { FieldView } from "./nodes/FieldView";
import { FormattedTextBox } from "./nodes/FormattedTextBox";
import { IconBox } from "./nodes/IconBox";
import React = require("react");
+import { TooltipTextMenu } from '../util/TooltipTextMenu';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -48,7 +49,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _resizeBorderWidth = 16;
private _linkBoxHeight = 20 + 3; // link button height + margin
private _titleHeight = 20;
- private _embedButton = React.createRef<HTMLDivElement>();
private _downX = 0;
private _downY = 0;
private _iconDoc?: Doc = undefined;
@@ -414,41 +414,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
- onEmbedButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onEmbedButtonMoved);
- document.addEventListener("pointermove", this.onEmbedButtonMoved);
- document.removeEventListener("pointerup", this.onEmbedButtonUp);
- document.addEventListener("pointerup", this.onEmbedButtonUp);
- }
-
-
-
- onEmbedButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onEmbedButtonMoved);
- document.removeEventListener("pointerup", this.onEmbedButtonUp);
- e.stopPropagation();
- }
-
- @action
- onEmbedButtonMoved = (e: PointerEvent): void => {
- if (this._embedButton.current !== null) {
- document.removeEventListener("pointermove", this.onEmbedButtonMoved);
- document.removeEventListener("pointerup", this.onEmbedButtonUp);
-
- let dragDocView = SelectionManager.SelectedDocuments()[0];
- let dragData = new DragManager.EmbedDragData(dragDocView.props.Document);
-
- DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, {
- handlers: {
- dragComplete: action(emptyFunction),
- },
- hideSource: false
- });
- }
- e.stopPropagation();
- }
-
onPointerMove = (e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
@@ -502,7 +467,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
break;
}
- if (!this._resizing) runInAction(() => FormattedTextBox.InputBoxOverlay = undefined);
SelectionManager.SelectedDocuments().forEach(element => {
if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
let doc = PositionDocument(element.props.Document);
@@ -582,6 +546,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
return "-unset-";
}
+ TextBar: HTMLDivElement | undefined;
+ setTextBar = (ele: HTMLDivElement) => {
+ if (ele) {
+ this.TextBar = ele;
+ TooltipTextMenu.Toolbar && Array.from(ele.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && ele.appendChild(TooltipTextMenu.Toolbar);
+ }
+ }
render() {
var bounds = this.Bounds;
let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
@@ -614,7 +585,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0,
}} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
</div>
- <div className="documentDecorations-container" style={{
+ <div className="documentDecorations-container" ref={this.setTextBar} style={{
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight + 3) + "px",
left: bounds.x - this._resizeBorderWidth / 2,
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 1cfa8d644..9ab320eab 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -87,7 +87,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
color: InkingControl.Instance.selectedColor,
width: InkingControl.Instance.selectedWidth,
tool: InkingControl.Instance.selectedTool,
- page: NumCast(this.props.Document.curPage, -1)
+ displayTimecode: NumCast(this.props.Document.currentTimecode, -1)
});
this.inkData = data;
}
@@ -150,9 +150,9 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
@computed
get drawnPaths() {
- let curPage = NumCast(this.props.Document.curPage, -1);
+ let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
- if (strokeData.page === -1 || (Math.abs(Math.round(strokeData.page) - Math.round(curPage)) < 3)) {
+ if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
paths.push(<InkingStroke key={id} id={id}
line={strokeData.pathData}
count={strokeData.pathData.length}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index a10df0e75..ee8b77050 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -10,7 +10,6 @@ import { InkTool } from "../../new_fields/InkField";
import { Doc } from "../../new_fields/Doc";
import { undoBatch, UndoManager } from "../util/UndoManager";
import { StrCast, NumCast, Cast } from "../../new_fields/Types";
-import { MainOverlayTextBox } from "./MainOverlayTextBox";
import { listSpec } from "../../new_fields/Schema";
import { List } from "../../new_fields/List";
import { Utils } from "../../Utils";
@@ -46,7 +45,7 @@ export class InkingControl extends React.Component {
switchColor = action((color: ColorResult): void => {
this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
if (InkingControl.Instance.selectedTool === InkTool.None) {
- if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
+ // if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
let selected = SelectionManager.SelectedDocuments();
let oldColors = selected.map(view => {
let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 04249506a..4cd860da2 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -238,6 +238,13 @@ ul#add-options-list {
flex-direction: column;
}
+.expandFlyoutButton {
+ position: absolute;
+ top: 30px;
+ right: 30px;
+ cursor: pointer;
+}
+
.mainView-libraryHandle {
width: 20px;
height: 40px;
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
deleted file mode 100644
index c9d44e194..000000000
--- a/src/client/views/MainOverlayTextBox.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-@import "globalCssVariables";
-
-.mainOverlayTextBox-textInput {
- background-color: rgba(248, 6, 6, 0.001);
- width: 400px;
- height: 200px;
- position: absolute;
- overflow: visible;
- top: 0;
- left: 0;
- pointer-events: none;
- z-index: $mainTextInput-zindex;
-
- .formattedTextBox-cont {
- background-color: rgba(248, 6, 6, 0.001);
- width: 100%;
- height: 100%;
- position: absolute;
- top: 0;
- left: 0;
- }
-}
-
-.mainOverlayTextBox-unscaled_div {
- // width: 0px;
- z-index: 10000;
- position: absolute;
- pointer-events: none;
-} \ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
deleted file mode 100644
index 335cc609f..000000000
--- a/src/client/views/MainOverlayTextBox.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import { action, observable, reaction, trace } from 'mobx';
-import { observer } from 'mobx-react';
-import "normalize.css";
-import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../new_fields/Doc';
-import { BoolCast } from '../../new_fields/Types';
-import { emptyFunction, returnTrue, returnZero, Utils, returnOne } from '../../Utils';
-import { DragManager } from '../util/DragManager';
-import { Transform } from '../util/Transform';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import "./MainOverlayTextBox.scss";
-import { FormattedTextBox } from './nodes/FormattedTextBox';
-
-interface MainOverlayTextBoxProps {
- firstinstance?: boolean;
-}
-
-@observer
-export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> {
- public static Instance: MainOverlayTextBox;
- @observable _textXf: () => Transform = () => Transform.Identity();
- public TextFieldKey: string = "data";
- private _textColor: string | null = null;
- private _textHideOnLeave?: boolean;
- private _textTargetDiv: HTMLDivElement | undefined;
- private _textProxyDiv: React.RefObject<HTMLDivElement>;
- private _textBottom: boolean | undefined;
- private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); };
- private _outerdiv: HTMLElement | null = null;
- private _textBox: FormattedTextBox | undefined;
- private _tooltip?: HTMLElement;
- ChromeHeight?: () => number;
- @observable public TextDoc?: Doc;
- @observable public TextDataDoc?: Doc;
-
- updateTooltip = () => {
- this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip);
- }
-
- public SetColor(color: string) {
- return this._textBox && this._textBox.setFontColor(color);
- }
-
- constructor(props: MainOverlayTextBoxProps) {
- super(props);
- this._textProxyDiv = React.createRef();
- MainOverlayTextBox.Instance = this;
- reaction(() => FormattedTextBox.InputBoxOverlay,
- (box?: FormattedTextBox) => {
- this._textBox = box;
- if (box) {
- this.ChromeHeight = box.props.ChromeHeight;
- this.TextDoc = box.props.Document;
- this.TextDataDoc = box.props.DataDoc;
- let xf = () => {
- box.props.ScreenToLocalTransform();
- let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
- return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale);
- };
- this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight) || box.props.height === "min-content");
- }
- else {
- this.TextDoc = undefined;
- this.TextDataDoc = undefined;
- this.setTextDoc();
- }
- });
- }
-
- @action
- private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform, autoHeight?: boolean) {
- if (this._textTargetDiv) {
- this._textTargetDiv.style.color = this._textColor;
- }
- this.TextFieldKey = textFieldKey!;
- let txf = tx ? tx : () => Transform.Identity();
- this._textXf = txf;
- this._textTargetDiv = div;
- this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave;
- if (div) {
- this._textBottom = div.parentElement && getComputedStyle(div.parentElement).top !== "0px" ? true : false;
- this._textColor = (getComputedStyle(div) as any).color;
- div.style.color = "transparent";
- }
- }
-
- @action
- textScroll = (e: React.UIEvent) => {
- if (this._textProxyDiv.current && this._textTargetDiv) {
- this._textTargetDiv.scrollTop = (e as any)._targetInst.stateNode.scrollTop;
- }
- }
-
- textBoxDown = (e: React.PointerEvent) => {
- if (e.button !== 0 || e.metaKey || e.altKey) {
- document.addEventListener("pointermove", this.textBoxMove);
- document.addEventListener('pointerup', this.textBoxUp);
- }
- }
- @action
- textBoxMove = (e: PointerEvent) => {
- if ((e.movementX > 1 || e.movementY > 1) && FormattedTextBox.InputBoxOverlay) {
- document.removeEventListener("pointermove", this.textBoxMove);
- document.removeEventListener('pointerup', this.textBoxUp);
- let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document]);
- const [left, top] = this._textXf().inverse().transformPoint(0, 0);
- dragData.offset = [e.clientX - left, e.clientY - top];
- DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {
- handlers: {
- dragComplete: action(emptyFunction),
- },
- hideSource: false
- });
- }
- }
- textBoxUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.textBoxMove);
- document.removeEventListener('pointerup', this.textBoxUp);
- }
-
- addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => {
- return this._textBox && this._textBox.props.addDocTab(doc, dataDoc, location) ? true : false;
- }
- render() {
- this.TextDoc; this.TextDataDoc;
- if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
- let wid = FormattedTextBox.InputBoxOverlay.props.Document.width; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations)
- let hgtx = FormattedTextBox.InputBoxOverlay.props.Document.height; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations)
- let textRect = this._textTargetDiv.getBoundingClientRect();
- let s = this._textXf().Scale;
- let location = this._textBottom ? textRect.bottom : textRect.top;
- let hgt = (this._textBox && this._textBox.props.Document.autoHeight) || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
- return <div ref={this._setouterdiv} className="mainOverlayTextBox-unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} >
- <div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s})`, width: "auto", height: "0px" }} >
- <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
- style={{ width: `${textRect.width * s}px`, height: "0px" }}>
- <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
- <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} fieldExt="" hideOnLeave={this._textHideOnLeave} isOverlay={true}
- Document={FormattedTextBox.InputBoxOverlay.props.Document}
- DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
- onClick={undefined}
- ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined}
- ChromeHeight={this.ChromeHeight} isSelected={returnTrue} select={emptyFunction} renderDepth={0}
- ContainingCollectionDoc={undefined} ContainingCollectionView={undefined}
- whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
- ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction}
- pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
- </div>
- </div>
- </div>
- </ div>;
- }
- else return (null);
- }
-} \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 35d527c91..12578e5b8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv, faChevronRight, faEllipsisV } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
@@ -33,14 +33,13 @@ import { DocumentDecorations } from './DocumentDecorations';
import KeyManager from './GlobalKeyHandler';
import { InkingControl } from './InkingControl';
import "./Main.scss";
-import { MainOverlayTextBox } from './MainOverlayTextBox';
import MainViewModal from './MainViewModal';
import { DocumentView } from './nodes/DocumentView';
-import { PresBox } from './nodes/PresBox';
-import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
import { FilterBox } from './search/FilterBox';
+import { OverlayView } from './OverlayView';
+import AuthenticationManager from '../apis/AuthenticationManager';
@observer
export class MainView extends React.Component {
@@ -213,6 +212,8 @@ export class MainView extends React.Component {
library.add(faArrowUp);
library.add(faCloudUploadAlt);
library.add(faBolt);
+ library.add(faChevronRight);
+ library.add(faEllipsisV);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -366,12 +367,14 @@ export class MainView extends React.Component {
}
@observable flyoutWidth: number = 250;
+ @observable flyoutTranslate: boolean = true;
@computed get dockingContent() {
let flyoutWidth = this.flyoutWidth;
+ let countWidth = this.flyoutTranslate;
let mainCont = this.mainContainer;
return <Measure offset onResize={this.onResize}>
{({ measureRef }) =>
- <div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${flyoutWidth}px`, transform: `translate(${flyoutWidth}px, 0px)` }} onDrop={this.onDrop}>
+ <div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${countWidth ? flyoutWidth : 0}px`, transform: `translate(${countWidth ? flyoutWidth : 0}px, 0px)` }} onDrop={this.onDrop}>
{!mainCont ? (null) :
<DocumentView Document={mainCont}
DataDoc={undefined}
@@ -411,6 +414,23 @@ export class MainView extends React.Component {
e.stopPropagation();
e.preventDefault();
}
+
+ @action
+ pointerOverDragger = () => {
+ if (this.flyoutWidth === 0) {
+ this.flyoutWidth = 250;
+ this.flyoutTranslate = false;
+ }
+ }
+
+ @action
+ pointerLeaveDragger = () => {
+ if (!this.flyoutTranslate) {
+ this.flyoutWidth = 0;
+ this.flyoutTranslate = true;
+ }
+ }
+
@action
onPointerMove = (e: PointerEvent) => {
this.flyoutWidth = Math.max(e.clientX, 0);
@@ -467,26 +487,52 @@ export class MainView extends React.Component {
getScale={returnOne}>
</DocumentView>;
}
+
@computed
get mainContent() {
if (!this.userDoc) {
- return <div>{this.dockingContent}</div>;
+ return (<div>{this.dockingContent}</div>);
}
let sidebar = this.userDoc.sidebar;
if (!(sidebar instanceof Doc)) {
return (null);
}
- return <div>
- <div className="mainView-libraryHandle"
- style={{ cursor: "ew-resize", left: `${this.flyoutWidth - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
- onPointerDown={this.onPointerDown}>
- <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} />
- </div>
- <div className="mainView-libraryFlyout" style={{ width: `${this.flyoutWidth}px`, zIndex: 1 }}>
- {this.flyout}
- </div>
- {this.dockingContent}
- </div>;
+ return (
+ <div className="mainContent" style={{ width: "100%", height: "100%", position: "absolute" }}>
+ <div onPointerLeave={this.pointerLeaveDragger}>
+ <div className="mainView-libraryHandle"
+ style={{ cursor: "ew-resize", left: `${(this.flyoutWidth * (this.flyoutTranslate ? 1 : 0)) - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
+ onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}>
+ <span title="library View Dragger" style={{
+ width: (this.flyoutWidth !== 0 && this.flyoutTranslate) ? "100%" : "5vw",
+ height: (this.flyoutWidth !== 0 && this.flyoutTranslate) ? "100%" : "30vh",
+ position: "absolute",
+ top: (this.flyoutWidth !== 0 && this.flyoutTranslate) ? "" : "-10vh"
+ }} />
+ </div>
+ <div className="mainView-libraryFlyout" style={{
+ width: `${this.flyoutWidth}px`,
+ zIndex: 1,
+ transformOrigin: this.flyoutTranslate ? "" : "left center",
+ transition: this.flyoutTranslate ? "" : "width .5s",
+ transform: `scale(${this.flyoutTranslate ? 1 : 0.8})`,
+ boxShadow: this.flyoutTranslate ? "" : "rgb(156, 147, 150) 0.2vw 0.2vw 0.8vw"
+ }}>
+ {this.flyout}
+ {this.expandButton}
+ </div>
+ </div>
+ {this.dockingContent}
+ </div>);
+ }
+
+ @computed get expandButton() {
+ return !this.flyoutTranslate ? (<div className="expandFlyoutButton" title="Re-attach sidebar" onPointerDown={() => {
+ runInAction(() => {
+ this.flyoutWidth = 250;
+ this.flyoutTranslate = true;
+ });
+ }}><FontAwesomeIcon icon="chevron-right" color="grey" size="lg" /></div>) : (null);
}
selected = (tool: InkTool) => {
@@ -550,7 +596,7 @@ export class MainView extends React.Component {
];
if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
- return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 20, bottom: 20 }} >
+ return < div id="add-nodes-menu" style={{ left: (this.flyoutTranslate ? this.flyoutWidth : 0) + 20, bottom: 20 }} >
<input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} />
<label htmlFor="add-menu-toggle" style={{ marginTop: 2 }} title="Close Menu"><p>+</p></label>
@@ -632,6 +678,7 @@ export class MainView extends React.Component {
<div id="main-div">
{this.dictationOverlay}
<SharingManager />
+ <AuthenticationManager />
<DocumentDecorations />
{this.mainContent}
<PreviewCursor />
@@ -639,7 +686,6 @@ export class MainView extends React.Component {
{this.nodesMenu()}
{this.miscButtons}
<PDFMenu />
- <MainOverlayTextBox firstinstance={true} />
<OverlayView />
</div >
);
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index f1b101b8e..41453f8b2 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -3,7 +3,7 @@ import "./MetadataEntryMenu.scss";
import { observer } from 'mobx-react';
import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
import { KeyValueBox } from './nodes/KeyValueBox';
-import { Doc, Field, DocListCast, DocListCastAsync } from '../../new_fields/Doc';
+import { Doc, Field, DocListCastAsync } from '../../new_fields/Doc';
import * as Autosuggest from 'react-autosuggest';
import { undoBatch } from '../util/UndoManager';
import { emptyFunction } from '../../Utils';
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index bb6dd5076..95c7b2683 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -145,6 +145,7 @@ export class OverlayView extends React.Component {
return (null);
}
return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => {
+ d.inOverlay = true;
let offsetx = 0, offsety = 0;
let onPointerMove = action((e: PointerEvent) => {
if (e.buttons === 1) {
@@ -170,14 +171,14 @@ export class OverlayView extends React.Component {
document.addEventListener("pointerup", onPointerUp);
};
return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)`, display: d.isMinimized ? "none" : "" }}>
- <DocumentContentsView
+ <DocumentView
Document={d}
ChromeHeight={returnZero}
- isSelected={returnFalse}
- select={emptyFunction}
- ruleProvider={undefined}
- layoutKey={"layout"}
+ // isSelected={returnFalse}
+ // select={emptyFunction}
+ // layoutKey={"layout"}
bringToFront={emptyFunction}
+ ruleProvider={undefined}
addDocument={undefined}
removeDocument={undefined}
ContentScaling={returnOne}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index e928887e2..62be1fc31 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -5,7 +5,7 @@ import { Doc, DocListCast } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
import { listSpec } from '../../../new_fields/Schema';
-import { BoolCast, Cast, NumCast, PromiseValue, StrCast } from '../../../new_fields/Types';
+import { BoolCast, Cast, NumCast, PromiseValue, StrCast, FieldValue } from '../../../new_fields/Types';
import { DocumentManager } from '../../util/DocumentManager';
import { SelectionManager } from '../../util/SelectionManager';
import { ContextMenu } from '../ContextMenu';
@@ -101,8 +101,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
@action.bound
addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
- var curPage = NumCast(this.props.Document.curPage, -1);
- Doc.GetProto(doc).page = curPage;
+ var curTime = NumCast(this.props.Document.currentTimecode, -1);
+ curTime !== -1 && (doc.displayTimecode = curTime);
if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer
Doc.GetProto(doc).annotationOn = this.props.Document;
}
@@ -130,8 +130,11 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
- PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
- annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined));
+ PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => {
+ if (Doc.AreProtosEqual(annotationOn, FieldValue(Cast(this.dataDoc.extendsDoc, Doc)))) {
+ Doc.GetProto(doc).annotationOn = undefined;
+ }
+ });
if (index !== -1) {
value.splice(index, 1);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 98aff41d3..fe805a980 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -31,7 +31,6 @@ import { SubCollectionViewProps } from "./CollectionSubView";
import React = require("react");
import { ButtonSelector } from './ParentDocumentSelector';
import { DocumentType } from '../../documents/DocumentTypes';
-import { compileFunction } from 'vm';
import { ComputedField } from '../../../new_fields/ScriptField';
library.add(faFile);
const _global = (window /* browser */ || global /* node */) as any;
@@ -574,8 +573,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
let curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc;
if (curPres) {
let pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
- Doc.GetProto(pinDoc).target = doc;
- Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.target instanceof Doc) && this.target.title.toString()');
+ Doc.GetProto(pinDoc).presentationTargetDoc = doc;
+ Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
const data = Cast(curPres.data, listSpec(Doc));
if (data) {
data.push(pinDoc);
@@ -614,8 +613,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
}
- panelWidth = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelWidth : Math.min(this._panelWidth, Math.max(NumCast(this._document!.width), this.nativeWidth()));
- panelHeight = () => this._document!.ignoreAspect || this._document!.fitWidth ? this._panelHeight : Math.min(this._panelHeight, Math.max(NumCast(this._document!.height), this.nativeHeight()));
+ panelWidth = () => this._document && this._document.maxWidth ? Math.min(Math.max(NumCast(this._document.width), NumCast(this._document.nativeWidth)), this._panelWidth) : this._panelWidth;
+ panelHeight = () => this._panelHeight;
nativeWidth = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeWidth) || this._panelWidth : 0;
nativeHeight = () => !this._document!.ignoreAspect && !this._document!.fitWidth ? NumCast(this._document!.nativeHeight) || this._panelHeight : 0;
@@ -644,7 +643,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
return Transform.Identity();
}
- get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() / this.ScreenToLocalTransform().Scale) / 2 : 0; }
+ get previewPanelCenteringOffset() { return this.nativeWidth() && !this._document!.ignoreAspect ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; }
addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => {
SelectionManager.DeselectAll();
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 24f585a07..1ba35a52b 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -51,7 +51,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
["title", ColumnType.String],
["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number],
["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
- ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number]
+ ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
]);
@observer
@@ -309,11 +309,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return resized;
}, [] as { id: string, value: number }[]);
}
- @computed get sorted(): { id: string, desc?: true }[] {
+ @computed get sorted(): { id: string, desc: boolean }[] {
return this.columns.reduce((sorted, shf) => {
shf.desc && sorted.push({ id: shf.heading, desc: shf.desc });
return sorted;
- }, [] as { id: string, desc?: true }[]);
+ }, [] as { id: string, desc: boolean }[]);
}
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 7ba9fb5ed..2a536a0f4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -32,6 +32,7 @@ export interface CollectionViewProps extends FieldViewProps {
moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
PanelWidth: () => number;
PanelHeight: () => number;
+ VisibleHeight?: () => number;
chromeCollapsed: boolean;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index ab01a1086..ca0c321b7 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -51,7 +51,7 @@
.editableView-container-editing {
display: block;
text-overflow: ellipsis;
- font-size: 24px;
+ font-size: 1vw;
white-space: nowrap;
}
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ffd1f9170..882a0f144 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -217,7 +217,7 @@ class TreeView extends React.Component<TreeViewProps> {
if (de.data instanceof DragManager.LinkDragData) {
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.props.document;
- DocUtils.MakeLink(sourceDoc, destDoc);
+ DocUtils.MakeLink({doc:sourceDoc}, {doc:destDoc});
e.stopPropagation();
}
if (de.data instanceof DragManager.DocumentDragData) {
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 5185d9d0e..3d898b7de 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -21,7 +21,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
}
private get uIButtons() {
let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
- let curTime = NumCast(this.props.Document.curPage);
+ let curTime = NumCast(this.props.Document.currentTimecode);
return ([<div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
<span>{"" + Math.round(curTime)}</span>
<span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
@@ -85,7 +85,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
onPointerMove = (e: PointerEvent) => {
this._isclick += Math.abs(e.movementX) + Math.abs(e.movementY);
if (this._videoBox) {
- this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.curPage, 0) + Math.sign(e.movementX) * 0.0333));
+ this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333));
}
e.stopImmediatePropagation();
}
@@ -94,7 +94,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
document.removeEventListener("pointermove", this.onPointerMove, true);
document.removeEventListener("pointerup", this.onPointerUp, true);
InkingControl.Instance.switchTool(InkTool.None);
- this._isclick < 10 && (this.props.Document.curPage = 0);
+ this._isclick < 10 && (this.props.Document.currentTimecode = 0);
}
setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index c4311fa52..bb1a12f88 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -46,12 +46,6 @@
border-radius: inherit;
box-sizing: border-box;
position: absolute;
- overflow: hidden;
-
- .marqueeView {
- overflow: hidden;
- }
-
top: 0;
left: 0;
width: 100%;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 27b9a06f5..38488f033 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -105,7 +105,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
}
- public isCurrent(doc: Doc) { return !this.props.Document.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); }
+ public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
public getActiveDocuments = () => {
return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
@@ -267,6 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerDown = (e: React.PointerEvent): void => {
+ if (e.nativeEvent.cancelBubble) return;
this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
document.removeEventListener("pointermove", this.onPointerMove);
@@ -338,7 +339,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.props.Document.lockedPosition || this.isAnnotationOverlay) return;
+ if (this.props.Document.lockedPosition || this.props.Document.inOverlay || this.isAnnotationOverlay) return;
if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
e.stopPropagation();
}
@@ -360,7 +361,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
setPan(panX: number, panY: number) {
- if (!this.props.Document.lockedPosition) {
+ if (!this.props.Document.lockedPosition || this.props.Document.inOverlay) {
this.props.Document.panTransformType = "None";
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -403,8 +404,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
SelectionManager.DeselectAll();
if (this.props.Document.scrollHeight) {
let annotOn = Cast(doc.annotationOn, Doc) as Doc;
- let offset = annotOn && (NumCast(annotOn.height) / 2);
- this.props.Document.scrollY = NumCast(doc.y) - offset;
+ if (!annotOn) {
+ this.props.focus(doc);
+ } else {
+ let contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height);
+ let offset = annotOn && (contextHgt / 2 * 96 / 72);
+ this.props.Document.scrollY = NumCast(doc.y) - offset;
+ }
} else {
const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
@@ -416,6 +422,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.setPan(newPanX, newPanY);
this.Document.panTransformType = "Ease";
+ Doc.BrushDoc(this.props.Document);
this.props.focus(this.props.Document);
willZoom && this.setScaleToZoom(doc, scale);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 9fc2e44fb..04f6ec2ad 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -6,6 +6,13 @@
width:100%;
height:100%;
}
+.marqueeView {
+ overflow: hidden;
+}
+
+.marqueeView:focus-within {
+ overflow: hidden;
+}
.marquee {
border-style: dashed;
box-sizing: border-box;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 82193aefa..eaf65b88c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -285,7 +285,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.props.removeDocument(d);
d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
- d.page = -1;
+ d.displayTimecode = undefined;
return d;
});
}
@@ -442,8 +442,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
render() {
let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0];
- return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
- <div style={{ position: "relative", transform: `translate(${p[0]}px, ${p[1]}px)` }} >
+ return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+ <div style={{ position: "relative", transform: `translate(${p[0]}px, ${p[1]}px)` }} onScroll={(e) => e.currentTarget.scrollLeft = 0} >
{this._visible ? this.marqueeDiv : null}
<div ref={this._mainCont} style={{ transform: `translate(${-p[0]}px, ${-p[1]}px)` }} >
{this.props.children}
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 72fff8e53..b18aa5d63 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -128,7 +128,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
runInAction(async () => {
this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest }));
this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
- let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.targetContext, Doc)) as Doc;
+ let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.anchor2Context, Doc)) as Doc;
runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest }));
});
}
@@ -152,26 +152,12 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
this.resetPan();
}
- unhighlight = () => {
- Doc.UnhighlightAll();
- document.removeEventListener("pointerdown", this.unhighlight);
- }
-
- @action
- highlightDoc = () => {
- if (LinkFollowBox.destinationDoc) {
- document.removeEventListener("pointerdown", this.unhighlight);
- Doc.HighlightDoc(LinkFollowBox.destinationDoc);
- window.setTimeout(() => {
- document.addEventListener("pointerdown", this.unhighlight);
- }, 10000);
- }
- }
+ highlightDoc = () => LinkFollowBox.destinationDoc && Doc.linkFollowHighlight(LinkFollowBox.destinationDoc);
@undoBatch
openFullScreen = () => {
if (LinkFollowBox.destinationDoc) {
- let view: DocumentView | null = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc);
+ let view = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc);
view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
}
}
@@ -235,45 +221,11 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
@undoBatch
jumpToLink = async (options: { shouldZoom: boolean }) => {
- if (LinkFollowBox.destinationDoc && LinkFollowBox.linkDoc) {
- let jumpToDoc: Doc = LinkFollowBox.destinationDoc;
- let pdfDoc = FieldValue(Cast(LinkFollowBox.destinationDoc, Doc));
- if (pdfDoc) {
- jumpToDoc = pdfDoc;
- }
- let proto = Doc.GetProto(LinkFollowBox.linkDoc);
- let targetContext = await Cast(proto.targetContext, Doc);
- let sourceContext = await Cast(proto.sourceContext, Doc);
- let guid = StrCast(LinkFollowBox.linkDoc[Id]);
- const shouldZoom = options ? options.shouldZoom : false;
-
- let dockingFunc = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
-
- if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext);
- }
- else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!));
- if (LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc) {
- if (guid) {
- let views = DocumentManager.Instance.getDocumentViews(jumpToDoc);
- views.length && (views[0].props.Document.scrollToLinkID = guid);
- } else {
- jumpToDoc.linkHref = Utils.prepend("/doc/" + StrCast(LinkFollowBox.linkDoc[Id]));
- }
- }
- }
- else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined,
- NumCast((LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 ? LinkFollowBox.linkDoc.anchor2Page : LinkFollowBox.linkDoc.anchor1Page)));
-
- }
- else {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc);
- }
+ if (LinkFollowBox.sourceDoc && LinkFollowBox.linkDoc) {
+ let focus = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
+ //let focus = (doc: Doc, maxLocation: string) => this.props.focus(docthis.props.focus(LinkFollowBox.destinationDoc, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation));
- this.highlightDoc();
- SelectionManager.DeselectAll();
+ DocumentManager.Instance.FollowLink(LinkFollowBox.linkDoc, LinkFollowBox.sourceDoc, focus, options && options.shouldZoom, false, undefined);
}
}
@@ -311,20 +263,23 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
openLinkInPlace = (options: { shouldZoom: boolean }) => {
if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) {
- let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
- let y = NumCast(LinkFollowBox.sourceDoc.y);
- let x = NumCast(LinkFollowBox.sourceDoc.x);
+ if (this.sourceView && this.sourceView.props.addDocument) {
+ let destViews = DocumentManager.Instance.getDocumentViews(LinkFollowBox.destinationDoc);
+ if (!destViews.find(dv => dv.props.ContainingCollectionView === this.sourceView!.props.ContainingCollectionView)) {
+ let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
+ let y = NumCast(LinkFollowBox.sourceDoc.y);
+ let x = NumCast(LinkFollowBox.sourceDoc.x);
- let width = NumCast(LinkFollowBox.sourceDoc.width);
- let height = NumCast(LinkFollowBox.sourceDoc.height);
+ let width = NumCast(LinkFollowBox.sourceDoc.width);
+ let height = NumCast(LinkFollowBox.sourceDoc.height);
- alias.x = x + width + 30;
- alias.y = y;
- alias.width = width;
- alias.height = height;
+ alias.x = x + width + 30;
+ alias.y = y;
+ alias.width = width;
+ alias.height = height;
- if (this.sourceView && this.sourceView.props.addDocument) {
- this.sourceView.props.addDocument(alias, false);
+ this.sourceView.props.addDocument(alias, false);
+ }
}
this.jumpToLink({ shouldZoom: options.shouldZoom });
diff --git a/src/client/views/nodes/Annotation.tsx b/src/client/views/nodes/Annotation.tsx
deleted file mode 100644
index 3e4ed6bf1..000000000
--- a/src/client/views/nodes/Annotation.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import "./ImageBox.scss";
-import React = require("react");
-import { observer } from "mobx-react";
-import { observable, action } from 'mobx';
-import 'react-pdf/dist/Page/AnnotationLayer.css';
-
-interface IProps {
- Span: HTMLSpanElement;
- X: number;
- Y: number;
- Highlights: any[];
- Annotations: any[];
- CurrAnno: any[];
-
-}
-
-/**
- * Annotation class is used to take notes on a particular highlight. You can also change highlighted span's color
- * Improvements to be made: Removing the annotation when onRemove is called. (Removing this, not just the highlighted span).
- * Also need to support multiline highlighting
- *
- * Written by: Andrew Kim
- */
-@observer
-export class Annotation extends React.Component<IProps> {
-
- /**
- * changes color of the span (highlighted section)
- */
- onColorChange = (e: React.PointerEvent) => {
- if (e.currentTarget.innerHTML === "r") {
- this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)";
- } else if (e.currentTarget.innerHTML === "b") {
- this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)";
- } else if (e.currentTarget.innerHTML === "y") {
- this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)";
- } else if (e.currentTarget.innerHTML === "g") {
- this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)";
- }
-
- }
-
- /**
- * removes the highlighted span. Supposed to remove Annotation too, but I don't know how to unmount this
- */
- @action
- onRemove = (e: any) => {
- let index: number = -1;
- //finding the highlight in the highlight array
- this.props.Highlights.forEach((e) => {
- for (const span of e.spans) {
- if (span === this.props.Span) {
- index = this.props.Highlights.indexOf(e);
- this.props.Highlights.splice(index, 1);
- }
- }
- });
-
- //removing from CurrAnno and Annotation array
- this.props.Annotations.splice(index, 1);
- this.props.CurrAnno.pop();
-
- //removing span from div
- if (this.props.Span.parentElement) {
- let nodesArray = this.props.Span.parentElement.childNodes;
- nodesArray.forEach((e) => {
- if (e === this.props.Span) {
- if (this.props.Span.parentElement) {
- this.props.Highlights.forEach((item) => {
- if (item === e) {
- item.remove();
- }
- });
- e.remove();
- }
- }
- });
- }
-
-
- }
-
- render() {
- return (
- <div
- style={{
- position: "absolute",
- top: "20px",
- left: "0px",
- zIndex: 1,
- transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
-
- }}>
- <div style={{ width: "200px", height: "50px", backgroundColor: "orange" }}>
- <button
- style={{ borderRadius: "25px", width: "25%", height: "100%" }}
- onClick={this.onRemove}
- >x</button>
- <div style={{ width: "75%", height: "100%", display: "inline-block" }}>
- <button onPointerDown={this.onColorChange} style={{ backgroundColor: "red", borderRadius: "50%", color: "transparent" }}>r</button>
- <button onPointerDown={this.onColorChange} style={{ backgroundColor: "blue", borderRadius: "50%", color: "transparent" }}>b</button>
- <button onPointerDown={this.onColorChange} style={{ backgroundColor: "yellow", borderRadius: "50%", color: "transparent" }}>y</button>
- <button onPointerDown={this.onColorChange} style={{ backgroundColor: "green", borderRadius: "50%", color: "transparent" }}>g</button>
- </div>
-
- </div>
- <div style={{ width: "200px", height: "200" }}>
- <textarea style={{ width: "100%", height: "100%" }}
- defaultValue="Enter Text Here..."
-
- ></textarea>
- </div>
- </div>
-
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 4ea200e6d..b3e7898c1 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -5,6 +5,7 @@
top: 0;
left:0;
border-radius: inherit;
+ transition : outline .3s linear;
// background: $light-color; //overflow: hidden;
transform-origin: left top;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ded50890d..a670d280d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -111,6 +111,7 @@ export const documentSchema = createSchema({
type: "string", // enumerated type of document
maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab)
lockedPosition: "boolean", // whether the document can be spatially manipulated
+ inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently
borderRounding: "string", // border radius rounding of document
searchFields: "string", // the search fields to display when this document matches a search in its metadata
heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc)
@@ -206,7 +207,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
- let linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
+ let linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
let expandedDocs: Doc[] = [];
expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
@@ -223,33 +224,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation)));
}
}
- else if (linkedDocs.length) {
- SelectionManager.DeselectAll();
- let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored);
- let second = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor2 as Doc, this.props.Document) && !d.anchor2anchored);
- let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
- let secondUnshown = second.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
- if (firstUnshown.length) first = [firstUnshown[0]];
- if (secondUnshown.length) second = [secondUnshown[0]];
- let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor1 as Doc] : undefined;
-
- // @TODO: shouldn't always follow target context
- let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined];
- let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined];
-
- if (linkedFwdDocs && !linkedFwdDocs.some(l => l instanceof Promise)) {
- let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab");
- let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionDoc) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
- DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false,
- // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards
- doc => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)),
- linkedFwdPage[altKey ? 1 : 0], targetContext);
- }
+ else if (linkDocs.length) {
+ DocumentManager.Instance.FollowLink(undefined, this.props.Document,
+ // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards
+ (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)),
+ ctrlKey, altKey, this.props.ContainingCollectionDoc);
}
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.nativeEvent.cancelBubble) return;
+ if (e.nativeEvent.cancelBubble && e.button === 0) return;
this._downX = e.clientX;
this._downY = e.clientY;
this._hitTemplateDrag = false;
@@ -260,17 +244,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._hitTemplateDrag = true;
}
}
- if (this.active && e.button === 0 && !this.Document.lockedPosition) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
+ if (this.active && e.button === 0 && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
+ if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
}
onPointerMove = (e: PointerEvent): void => {
+ if ((e as any).formattedHandled) { e.stopPropagation(); return; }
if (e.cancelBubble && this.active) {
document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
}
- else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive()) && !this.Document.lockedPosition) {
+ else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive()) && !this.Document.lockedPosition && !this.Document.inOverlay) {
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
if (!e.altKey && !this.topMost && e.buttons === 1) {
document.removeEventListener("pointermove", this.onPointerMove);
@@ -352,15 +338,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (de.data instanceof DragManager.AnnotationDragData) {
/// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner
e.stopPropagation();
- let sourceDoc = de.data.annotationDocument;
- let targetDoc = this.props.Document;
- let annotations = await DocListCastAsync(sourceDoc.annotations);
- sourceDoc.linkedToDoc = true;
- de.data.targetContext = this.props.ContainingCollectionDoc;
- targetDoc.targetContext = de.data.targetContext;
- annotations && annotations.forEach(anno => anno.target = targetDoc);
-
- DocUtils.MakeLink(sourceDoc, targetDoc, this.props.ContainingCollectionDoc, `Link from ${StrCast(sourceDoc.title)}`);
+ (de.data as any).linkedToDoc = true;
+
+ DocUtils.MakeLink({ doc: de.data.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.data.annotationDocument.title)}`);
}
if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document);
@@ -371,7 +351,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
// const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
de.data.linkSourceDocument !== this.props.Document &&
- (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc, undefined, "in-text link being created")); // TODODO this is where in text links get passed
+ (de.data.linkDocument = DocUtils.MakeLink({ doc: de.data.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
}
}
@@ -407,7 +387,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
DocServer.GetRefField(portalID).then(existingPortal => {
let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID });
- DocUtils.MakeLink(this.props.Document, portal, undefined, portalID);
+ DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link");
this.Document.isButton = true;
});
}
@@ -635,6 +615,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
DataDoc={this.props.DataDoc} />);
}
render() {
+ let animDims = this.props.Document.animateToDimensions ? Array.from(Cast(this.props.Document.animateToDimensions, listSpec("number"))!) : undefined;
const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
const colorSet = this.setsLayoutProp("backgroundColor");
@@ -644,7 +625,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
const nativeWidth = this.props.Document.fitWidth ? this.props.PanelWidth() : this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%";
- const nativeHeight = this.props.Document.fitWidth ? this.props.PanelHeight() : this.Document.ignoreAspect || this.props.Document.fitWidth ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%";
+ const nativeHeight = this.props.Document.fitWidth ? this.props.PanelHeight() : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle");
const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption");
@@ -678,6 +659,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true}
/>
</div>);
+ let animheight = animDims ? animDims[1] : nativeHeight;
+ let animwidth = animDims ? animDims[0] : nativeWidth;
+
+ const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
+ const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
return (
<div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
ref={this._mainCont}
@@ -685,13 +671,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition),
pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all",
color: StrCast(this.Document.color),
- outlineColor: ["transparent", "maroon", "maroon", "yellow"][fullDegree],
- outlineStyle: ["none", "dashed", "solid", "solid"][fullDegree],
- outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px",
- border: fullDegree && borderRounding ? `${["none", "dashed", "solid", "solid"][fullDegree]} ${["transparent", "maroon", "maroon", "yellow"][fullDegree]} ${localScale}px` : undefined,
+ outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
+ border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
background: backgroundColor,
- width: nativeWidth,
- height: nativeHeight,
+ width: animwidth,
+ height: animheight,
transform: `scale(${this.props.Document.fitWidth ? 1 : this.props.ContentScaling()})`,
opacity: this.Document.opacity
}}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 45e516015..29e8b14a8 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -28,7 +28,7 @@
}
.formattedTextBox-cont-hidden {
- pointer-events: none;
+ // pointer-events: none;
}
.formattedTextBox-inner-rounded {
@@ -40,9 +40,10 @@
left: 10%;
}
-.formattedTextBox-inner-rounded div,
-.formattedTextBox-inner div {
+.formattedTextBox-inner-rounded ,
+.formattedTextBox-inner {
padding: 10px 10px;
+ height: 100%;
}
.menuicon {
@@ -153,17 +154,18 @@ footnote::after {
content: "...";
}
-ol { counter-reset: deci1 0;}
-.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 }
-.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 }
-.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 }
-.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 }
-.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 }
-.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 }
-.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 }
-.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18 }
+.ProseMirror {
+ol { counter-reset: deci1 0; padding-left: 0px; }
+.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 ; ul, ol { padding-left:30px; } }
+.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 ; ul, ol { padding-left:30px; } }
+.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 ; ul, ol { padding-left:30px; } }
+.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
+.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
+.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
+.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
+.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18; }
.lower-roman-ol {counter-reset: lroman; p { display: inline }; font-size: 14; }
-.lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10;}
+.lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10; }
.decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;}
.decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35}
.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35}
@@ -174,3 +176,4 @@ ol { counter-reset: deci1 0;}
.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 }
.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 }
.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35}
+}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 449f56b16..67d1bc42c 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,53 +1,54 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
+import _ from "lodash";
import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
+import { inputRules } from 'prosemirror-inputrules';
import { keymap } from "prosemirror-keymap";
-import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } from "prosemirror-model";
-import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
+import { Fragment, Mark, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model";
+import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
+import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../new_fields/DateField';
-import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCastAsync, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
import { Copy, Id } from '../../../new_fields/FieldSymbols';
-import { List } from '../../../new_fields/List';
import { RichTextField } from "../../../new_fields/RichTextField";
-import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types";
+import { RichTextUtils } from '../../../new_fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Utils, numberRange, timenow } from '../../../Utils';
+import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from '../../../Utils';
+import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DictationManager } from '../../util/DictationManager';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from "../../util/DragManager";
import buildKeymap from "../../util/ProsemirrorExampleTransfer";
import { inpRules } from "../../util/RichTextRules";
-import { ImageResizeView, schema, SummarizedView, OrderedListView, FootnoteView } from "../../util/RichTextSchema";
+import { FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema";
import { SelectionManager } from "../../util/SelectionManager";
import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
import { TooltipTextMenu } from "../../util/TooltipTextMenu";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocComponent } from "../DocComponent";
+import { DocumentButtonBar } from '../DocumentButtonBar';
+import { DocumentDecorations } from '../DocumentDecorations';
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
+import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
import React = require("react");
-import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
-import { DocumentDecorations } from '../DocumentDecorations';
-import { DictationManager } from '../../util/DictationManager';
-import { ReplaceStep } from 'prosemirror-transform';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { RichTextUtils } from '../../../new_fields/RichTextUtils';
-import _ from "lodash";
-import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment';
-import { inputRules } from 'prosemirror-inputrules';
-import { DocumentButtonBar } from '../DocumentButtonBar';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { ContextMenu } from '../ContextMenu';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
export interface FormattedTextBoxProps {
- isOverlay?: boolean;
hideOnLeave?: boolean;
height?: string;
color?: string;
@@ -78,7 +79,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
private _proseRef?: HTMLDivElement;
private _editorView: Opt<EditorView>;
private _applyingChange: boolean = false;
- private _linkClicked = "";
private _nodeClicked: any;
private _undoTyping?: UndoManager.Batch;
private _searchReactionDisposer?: Lambda;
@@ -96,9 +96,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@observable private _fontFamily = "Arial";
@observable private _fontAlign = "";
@observable private _entered = false;
- @observable public static InputBoxOverlay?: FormattedTextBox = undefined;
public static SelectOnLoad = "";
- public static InputBoxOverlayScroll: number = 0;
public static IsFragment(html: string) {
return html.indexOf("data-pm-slice") !== -1;
}
@@ -120,8 +118,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
return "";
}
- public static getToolTip() {
- return this._toolTipTextMenu;
+ public static getToolTip(ev: EditorView) {
+ return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev);
}
@undoBatch
@@ -138,55 +136,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
constructor(props: FieldViewProps) {
super(props);
- if (this.props.isOverlay) {
- DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
- }
FormattedTextBox.Instance = this;
- this._scrollToRegionReactionDisposer = reaction(
- () => StrCast(this.props.Document.scrollToLinkID),
- async (scrollToLinkID) => {
- let findLinkFrag = (frag: Fragment, editor: EditorView) => {
- const nodes: Node[] = [];
- frag.forEach((node, index) => {
- let examinedNode = findLinkNode(node, editor);
- if (examinedNode && examinedNode.textContent) {
- nodes.push(examinedNode);
- start += index;
- }
- });
- return { frag: Fragment.fromArray(nodes), start: start };
- };
- let findLinkNode = (node: Node, editor: EditorView) => {
- if (!node.isText) {
- const content = findLinkFrag(node.content, editor);
- return node.copy(content.frag);
- }
- const marks = [...node.marks];
- const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
- return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
- };
-
- let start = -1;
- if (this._editorView && scrollToLinkID) {
- let editor = this._editorView;
- let ret = findLinkFrag(editor.state.doc.content, editor);
-
- if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) {
- let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
- if (ret.frag.firstChild) {
- selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
- }
- editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
- const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
- setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
- setTimeout(() => this.unhighlightSearchTerms(), 2000);
- }
- this.props.Document.scrollToLinkID = undefined;
- }
-
- },
- { fireImmediately: true }
- );
}
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
@@ -195,29 +145,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
- // this should be internal to prosemirror, but is needed
- // here to make sure that footnote view nodes in the overlay editor
- // get removed when they're not selected.
-
- syncNodeSelection(view: any, sel: any) {
- if (sel instanceof NodeSelection) {
- var desc = view.docView.descAt(sel.from);
- if (desc !== view.lastSelectedViewDesc) {
- if (view.lastSelectedViewDesc) {
- view.lastSelectedViewDesc.deselectNode();
- view.lastSelectedViewDesc = null;
- }
- if (desc) { desc.selectNode(); }
- view.lastSelectedViewDesc = desc;
- }
- } else {
- if (view.lastSelectedViewDesc) {
- view.lastSelectedViewDesc.deselectNode();
- view.lastSelectedViewDesc = null;
- }
- }
- }
-
linkOnDeselect: Map<string, string> = new Map();
doLinkOnDeselect() {
@@ -230,7 +157,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
- else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true);
+ else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id);
});
});
});
@@ -263,7 +190,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- this.syncNodeSelection(this._editorView, this._editorView.state.selection); // bcz: ugh -- shouldn't be needed but without this the overlay view's footnote popup doesn't get deselected
if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
}
@@ -335,14 +261,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
// We're dealing with a link to a document
- if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) {
+ if (de.data instanceof DragManager.EmbedDragData) {
let target = de.data.embeddableSourceDoc;
// We're dealing with an internal document drop
- let url = de.data.urlField.url.href;
- let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image;
+ const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title);
+ let alias = Doc.MakeAlias(target);
+ alias.fitToBox = true;
+ let node = schema.nodes.dashDoc.create({
+ width: target[WidthSym](), height: target[HeightSym](),
+ title: "dashDoc", docid: alias[Id],
+ float: "right"
+ });
let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
- this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] })));
- DocUtils.MakeLink(this.dataDoc, target, undefined, "ImgRef:" + target.title, undefined, undefined, target[Id]);
+ link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node));
this.tryUpdateHeight();
e.stopPropagation();
} else if (de.data instanceof DragManager.DocumentDragData) {
@@ -353,7 +284,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data);
e.stopPropagation();
}
- } else {
+ } else if (de.mods === "CtrlKey") {
draggedDoc.isTemplate = true;
if (typeof (draggedDoc.layout) === "string") {
let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc);
@@ -368,16 +299,58 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
- recordKeyHandler = (e: KeyboardEvent) => {
- if (SelectionManager.SelectedDocuments().length && this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) {
- if (e.key === "R" && e.altKey) {
- e.stopPropagation();
- e.preventDefault();
- this.recordBullet();
- }
+
+ static _highlights: string[] = [];
+
+ updateHighlights = () => {
+ clearStyleSheetRules(FormattedTextBox._userStyleSheet);
+ if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "yellow" });
+ }
+ if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" });
+ }
+ if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" });
+ }
+ if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
+ }
+ if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "0" });
+ }
+ if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ let min = Math.round(Date.now() / 1000 / 60);
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
+ setTimeout(() => this.updateHighlights());
+ }
+ if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
+ let hr = Math.round(Date.now() / 1000 / 60 / 60);
+ numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
}
+ specificContextMenu = (e: React.MouseEvent): void => {
+ let funcs: ContextMenuProps[] = [];
+ funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
+ ["My Text", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+ funcs.push({
+ description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+ e.stopPropagation();
+ if (FormattedTextBox._highlights.indexOf(option) === -1) {
+ FormattedTextBox._highlights.push(option);
+ } else {
+ FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+ }
+ this.updateHighlights();
+ }, icon: "expand-arrows-alt"
+ }));
+
+ ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: funcs, icon: "asterisk" });
+ }
+
recordBullet = async () => {
let completedCue = "end session";
let results = await DictationManager.Controls.listen({
@@ -408,12 +381,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
nextBullet = (pos: number) => {
if (this._editorView) {
let frag = Fragment.fromArray(this.newListItems(2));
- let slice = new Slice(frag, 2, 2);
- let state = this._editorView.state;
- this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
- pos += 4;
- state = this._editorView.state;
- this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+ if (this._editorView.state.doc.resolve(pos).depth >= 2) {
+ let slice = new Slice(frag, 2, 2);
+ let state = this._editorView.state;
+ this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
+ pos += 4;
+ state = this._editorView.state;
+ this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+ }
}
}
@@ -424,9 +399,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
_keymap: any = undefined;
@computed get config() {
this._keymap = buildKeymap(schema);
+ (schema as any).Document = this.props.Document;
return {
schema,
- plugins: this.props.isOverlay ? [
+ plugins: [
inputRules(inpRules),
this.tooltipTextMenuPlugin(),
history(),
@@ -439,26 +415,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}),
formattedTextBoxCommentPlugin
- ] : [
- history(),
- keymap(this._keymap),
- keymap(baseKeymap),
- ]
+ ]
};
}
componentDidMount() {
-
- if (!this.props.isOverlay) {
- this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
- () => {
- if (this.props.isSelected()) {
- FormattedTextBox.InputBoxOverlay = this;
- FormattedTextBox.InputBoxOverlayScroll = this._ref.current!.scrollTop;
- }
- }, { fireImmediately: true });
- }
-
this.pullFromGoogleDoc(this.checkState);
this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
@@ -519,11 +480,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._searchReactionDisposer = reaction(() => {
return StrCast(this.props.Document.search_string);
}, searchString => {
- const fieldkey = 'preview';
- let preview = false;
- // if (!this._editorView && Object.keys(this.props.Document).indexOf(fieldkey) !== -1) {
- // preview = true;
- // }
if (searchString) {
this.highlightSearchTerms([searchString]);
}
@@ -547,7 +503,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
},
action((rules: any) => {
this._fontFamily = rules ? rules.font : "Arial";
- this._fontSize = rules ? rules.size : 13;
+ this._fontSize = rules ? rules.size : NumCast(this.props.Document.fontSize, 13);
rules && setTimeout(() => {
const view = this._editorView!;
if (this._proseRef) {
@@ -563,6 +519,51 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}, 0);
}), { fireImmediately: true }
);
+ this._scrollToRegionReactionDisposer = reaction(
+ () => StrCast(this.props.Document.scrollToLinkID),
+ async (scrollToLinkID) => {
+ let findLinkFrag = (frag: Fragment, editor: EditorView) => {
+ const nodes: Node[] = [];
+ frag.forEach((node, index) => {
+ let examinedNode = findLinkNode(node, editor);
+ if (examinedNode && examinedNode.textContent) {
+ nodes.push(examinedNode);
+ start += index;
+ }
+ });
+ return { frag: Fragment.fromArray(nodes), start: start };
+ };
+ let findLinkNode = (node: Node, editor: EditorView) => {
+ if (!node.isText) {
+ const content = findLinkFrag(node.content, editor);
+ return node.copy(content.frag);
+ }
+ const marks = [...node.marks];
+ const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
+ return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
+ };
+
+ let start = -1;
+ if (this._editorView && scrollToLinkID) {
+ let editor = this._editorView;
+ let ret = findLinkFrag(editor.state.doc.content, editor);
+
+ if (ret.frag.size > 2 && ret.start >= 0) {
+ let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
+ if (ret.frag.firstChild) {
+ selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+ }
+ editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
+ const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+ setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
+ setTimeout(() => this.unhighlightSearchTerms(), 2000);
+ }
+ this.props.Document.scrollToLinkID = undefined;
+ }
+
+ },
+ { fireImmediately: true }
+ );
setTimeout(() => this.tryUpdateHeight(), 0);
}
@@ -667,55 +668,42 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
let cbe = event as ClipboardEvent;
- let docId: string;
- let regionId: string;
- if (!cbe.clipboardData) {
- return false;
- }
- let linkId: string;
- docId = cbe.clipboardData.getData("dash/pdfOrigin");
- regionId = cbe.clipboardData.getData("dash/pdfRegion");
- if (!docId || !regionId) {
- return false;
- }
-
- DocServer.GetRefField(docId).then(doc => {
- DocServer.GetRefField(regionId).then(region => {
- if (!(doc instanceof Doc) || !(region instanceof Doc)) {
- return;
- }
-
- let annotations = DocListCast(region.annotations);
- annotations.forEach(anno => anno.target = this.props.Document);
- let fieldExtDoc = Doc.fieldExtensionDoc(doc, "data");
- let targetAnnotations = DocListCast(fieldExtDoc.annotations);
- if (targetAnnotations) {
- targetAnnotations.push(region);
- fieldExtDoc.annotations = new List<Doc>(targetAnnotations);
- }
+ const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin");
+ const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion");
+ if (pdfDocId && pdfRegionId) {
+ DocServer.GetRefField(pdfDocId).then(pdfDoc => {
+ DocServer.GetRefField(pdfRegionId).then(pdfRegion => {
+ if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) {
+ setTimeout(async () => {
+ let targetAnnotations = await DocListCastAsync(Doc.fieldExtensionDoc(pdfDoc, "data").annotations);// bcz: NO... this assumes the pdf is using its 'data' field. need to have the PDF's view handle updating its own annotations
+ targetAnnotations && targetAnnotations.push(pdfRegion);
+ });
- let link = DocUtils.MakeLink(this.props.Document, region, doc);
- if (link) {
- cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
- linkId = link[Id];
- let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(doc.title)));
- slice = new Slice(frag, slice.openStart, slice.openEnd);
- var tr = view.state.tr.replaceSelection(slice);
- view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
- }
+ let link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link");
+ if (link) {
+ cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
+ let linkId = link[Id];
+ let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
+ slice = new Slice(frag, slice.openStart, slice.openEnd);
+ var tr = view.state.tr.replaceSelection(slice);
+ view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
+ }
+ }
+ });
});
- });
+ return true;
+ }
+ return false;
- return true;
function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
const nodes: Node[] = [];
frag.forEach(node => nodes.push(marker(node)));
return Fragment.fromArray(nodes);
}
- function addLinkMark(node: Node, title: string) {
+ function addLinkMark(node: Node, title: string, linkId: string) {
if (!node.isText) {
- const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title));
+ const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
return node.copy(content);
}
const marks = [...node.marks];
@@ -754,6 +742,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
},
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
+ dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
ordered_list(node, view, getPos) { return new OrderedListView(); },
@@ -762,7 +751,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- (this._editorView as any).isOverlay = this.props.isOverlay;
if (startup) {
Doc.GetProto(doc).documentText = undefined;
this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
@@ -774,9 +762,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
}
- else if (this.props.isOverlay) this._editorView!.focus();
+ this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
- this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })];
+ this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) })];
}
getFont(font: string) {
switch (font) {
@@ -803,8 +791,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._searchReactionDisposer && this._searchReactionDisposer();
this._editorView && this._editorView.destroy();
}
-
-
onPointerDown = (e: React.PointerEvent): void => {
let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
@@ -814,31 +800,25 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
}
- let ctrlKey = e.ctrlKey;
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
}
onPointerUp = (e: React.PointerEvent): void => {
- FormattedTextBoxComment.textBox = this;
+ if (!(e.nativeEvent as any).formattedHandled) { FormattedTextBoxComment.textBox = this; }
+ (e.nativeEvent as any).formattedHandled = true;
+
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
}
+ static InputBoxOverlay: FormattedTextBox | undefined;
@action
onFocused = (e: React.FocusEvent): void => {
- document.removeEventListener("keypress", this.recordKeyHandler);
- document.addEventListener("keypress", this.recordKeyHandler);
+ FormattedTextBox.InputBoxOverlay = this;
this.tryUpdateHeight();
- if (!this.props.isOverlay) {
- FormattedTextBox.InputBoxOverlay = this;
- } else {
- if (this._ref.current) {
- this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll;
- }
- }
}
onPointerWheel = (e: React.WheelEvent): void => {
// if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
@@ -847,17 +827,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ static _bulletStyleSheet: any = addStyleSheet();
+ static _userStyleSheet: any = addStyleSheet();
+
onClick = (e: React.MouseEvent): void => {
- let ctrlKey = e.ctrlKey;
+ if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
+ (e.nativeEvent as any).formattedHandled = true;
if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
let href = (e.target as any).href;
let location: string;
if ((e.target as any).attributes.location) {
location = (e.target as any).attributes.location.value;
}
- for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
- href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
- }
let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
if (node) {
@@ -869,57 +850,42 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- if (this._linkClicked) {
- DocServer.GetRefField(this._linkClicked).then(async linkDoc => {
- if (linkDoc instanceof Doc) {
- let proto = Doc.GetProto(linkDoc);
- let targetContext = await Cast(proto.targetContext, Doc);
- let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
-
- if (jumpToDoc) {
- if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
- return;
- }
- }
- if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), undefined, targetContext);
- } else if (jumpToDoc) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
- } else {
- DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
- }
- }
+ let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ if (linkClicked) {
+ DocServer.GetRefField(linkClicked).then(async linkDoc => {
+ (linkDoc instanceof Doc) &&
+ DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false);
});
- e.stopPropagation();
- e.preventDefault();
}
} else {
let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) });
this.props.addDocument && this.props.addDocument(webDoc);
- this._linkClicked = webDoc[Id];
}
e.stopPropagation();
e.preventDefault();
}
-
}
- // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
- if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
- let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+
+ this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey);
+ }
+
+ // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
+ hitBulletTargets(x: number, y: number, offsetX: number, select: boolean = false) {
+ clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
+ if (this.props.isSelected() && offsetX < 40) {
+ let pos = this._editorView!.posAtCoords({ left: x, top: y });
if (pos && pos.pos > 0) {
let node = this._editorView!.state.doc.nodeAt(pos.pos);
let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined;
if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) {
- let hit = this._editorView!.domAtPos(pos.pos).node as any;
- let beforeEle = document.querySelector("." + hit.className) as Element;
- let before = beforeEle ? window.getComputedStyle(beforeEle, ':before') : undefined;
+ let hit = this._editorView!.domAtPos(pos.pos).node as any; // let beforeEle = document.querySelector("." + hit.className) as Element;
+ let before = hit ? window.getComputedStyle(hit, ':before') : undefined;
let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined;
- if (beforeWidth && e.nativeEvent.offsetX < beforeWidth) {
+ if (beforeWidth && offsetX < beforeWidth) {
let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined;
- if (ol && ol.type === schema.nodes.ordered_list && !e.shiftKey) {
+ if (ol && ol.type === schema.nodes.ordered_list && select) {
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2))));
+ addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "gray" });
} else {
this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
}
@@ -927,25 +893,28 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
}
- this._proseRef!.focus();
- if (this._linkClicked) {
- this._linkClicked = "";
- e.preventDefault();
- e.stopPropagation();
- }
}
- onMouseDown = (e: React.MouseEvent): void => {
- if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself
- e.preventDefault(); // bcz: this would normally be in OnPointerDown - however, if done there, no mouse move events will be generated which makes transititioning to GoldenLayout's drag interactions impossible
+ onMouseUp = (e: React.MouseEvent): void => {
+ e.stopPropagation();
+
+ // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there are nested prosemirrors. We only want the lowest level prosemirror to be invoked.
+ if ((this._editorView as any).mouseDown) {
+ let originalUpHandler = (this._editorView as any).mouseDown.up;
+ (this._editorView as any).root.removeEventListener("mouseup", originalUpHandler);
+ (this._editorView as any).mouseDown.up = (e: MouseEvent) => {
+ !(e as any).formattedHandled && originalUpHandler(e);
+ e.stopPropagation();
+ (e as any).formattedHandled = true;
+ };
+ (this._editorView as any).root.addEventListener("mouseup", (this._editorView as any).mouseDown.up);
}
}
tooltipTextMenuPlugin() {
- let myprops = this.props;
let self = FormattedTextBox;
return new Plugin({
- view(_editorView) {
- return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
+ view(newView) {
+ return self._toolTipTextMenu = FormattedTextBox.getToolTip(newView);
}
});
}
@@ -959,7 +928,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
});
}
onBlur = (e: any) => {
- document.removeEventListener("keypress", this.recordKeyHandler);
+ DictationManager.Controls.stop(false);
if (this._undoTyping) {
this._undoTyping.end();
this._undoTyping = undefined;
@@ -974,7 +943,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (e.key === "Tab" || e.key === "Enter") {
e.preventDefault();
}
- this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })));
+ let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+ this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
if (!this._undoTyping) {
this._undoTyping = UndoManager.StartBatch("undoTyping");
@@ -985,7 +955,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
tryUpdateHeight() {
const ChromeHeight = this.props.ChromeHeight;
let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
- if (!this.props.isOverlay && !this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0) {
+ if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") {
let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
let dh = NumCast(this.props.Document.height, 0);
this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0));
@@ -994,30 +964,34 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
render() {
- let style = this.props.isOverlay ? "scroll" : "hidden";
+ let style = "hidden";
let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";
let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground
? "none" : "all";
Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
+ if (this.props.isSelected()) {
+ FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
+ }
return (
<div className={`formattedTextBox-cont-${style}`} ref={this._ref}
style={{
overflowY: this.props.Document.autoHeight ? "hidden" : "auto",
height: this.props.Document.autoHeight ? "max-content" : this.props.height ? this.props.height : undefined,
background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
- opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1,
+ opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
pointerEvents: interactive,
fontSize: this._fontSize,
fontFamily: this._fontFamily,
}}
+ onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyPress}
onFocus={this.onFocused}
onClick={this.onClick}
onBlur={this.onBlur}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
- onMouseDown={this.onMouseDown}
+ onMouseUp={this.onMouseUp}
onWheel={this.onPointerWheel}
onPointerEnter={action(() => this._entered = true)}
onPointerLeave={action(() => this._entered = false)}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index 2d2fff06d..a75bfd373 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -7,6 +7,7 @@ import { schema } from "../../util/RichTextSchema";
import { DocServer } from "../../DocServer";
import { Utils } from "../../../Utils";
import { StrCast } from "../../../new_fields/Types";
+import { FormattedTextBox } from "./FormattedTextBox";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -49,7 +50,7 @@ export class FormattedTextBoxComment {
static end: number;
static mark: Mark;
static opened: boolean;
- static textBox: any;
+ static textBox: FormattedTextBox | undefined;
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
const root = document.getElementById("root");
@@ -62,9 +63,9 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
FormattedTextBoxComment.tooltip.appendChild(input);
FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
- let keep = e.target && (e.target as any).type === "checkbox";
+ let keep = e.target && (e.target as any).type === "checkbox" ? true : false;
FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
- FormattedTextBoxComment.textBox && FormattedTextBoxComment.textBox.setAnnotation(
+ FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation(
FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
FormattedTextBoxComment.opened, keep);
};
@@ -92,8 +93,9 @@ export class FormattedTextBoxComment {
if (lastState && lastState.doc.eq(state.doc) &&
lastState.selection.eq(state.selection)) return;
+ if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props.isSelected()) return;
let set = "none";
- if (state.selection.$from) {
+ if (FormattedTextBoxComment.textBox && state.selection.$from) {
let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
const spos = state.selection.$from.pos - nbef;
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 63a504d1a..f3adade58 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -77,9 +77,9 @@ export class IconBox extends React.Component<FieldViewProps> {
<div className="iconBox-container" onContextMenu={this.specificContextMenu}>
{this.minimizedIcon}
<Measure offset onResize={(r) => runInAction(() => {
- if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) {
- this.props.Document.nativeWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE));
- if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.nativeWidth;
+ if (r.offset!.width || this.props.Document.hideLabel) {
+ this.props.Document.iconWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE));
+ if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.iconWidth;
}
})}>
{({ measureRef }) =>
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 9e9fe1324..a198a0764 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -299,7 +299,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
let rotation = NumCast(this.dataDoc.rotation) % 180;
let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
let aspect = realsize.height / realsize.width;
- if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1)) {
+ if (layoutdoc.width && (Math.abs(1 - NumCast(layoutdoc.height) / NumCast(layoutdoc.width) / (realsize.height / realsize.width)) > 0.1)) {
setTimeout(action(() => {
layoutdoc.height = layoutdoc[WidthSym]() * aspect;
layoutdoc.nativeHeight = realsize.height;
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 3f5f47e05..1c1d6ec95 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -10,12 +10,12 @@
}
.pdfBox-title-outer {
-
+ z-index: 0;
position: absolute;
width: 100%;
height: 100%;
background: lightslategray;
- .pdfBox-title-cont {
+ .pdfBox-title-cont, .pdfBox-cont-interactive{
width: 150%;
height: 100%;
position: relative;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3ad0b4010..1f3887608 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -57,20 +57,17 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
loaded = (nw: number, nh: number, np: number) => {
this.dataDoc.numPages = np;
- if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) {
- let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
- this.Document.nativeWidth = nw * 96 / 72;
- this.Document.nativeHeight = this.Document.nativeHeight ? nw * 96 / 72 * oldaspect : nh * 96 / 72;
- }
+ this.Document.nativeWidth = nw * 96 / 72;
+ this.Document.nativeHeight = nh * 96 / 72;
!this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
}
public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); }
public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); }
public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); }
- public backPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) - 1); }
+ public backPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); }
public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); };
- public forwardPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) + 1); }
+ public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); }
@undoBatch
@action
@@ -131,7 +128,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
<div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
<FontAwesomeIcon style={{ color: "white", padding: 5 }} icon={this._searching ? "times" : "search"} size="3x" /></div>
</button>
- <input value={`${NumCast(this.props.Document.curPage)}`}
+ <input value={`${(this.Document.curPage || 1)}`}
onChange={e => this.gotoPage(Number(e.currentTarget.value))}
style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }}
onClick={action(() => this._pageControls = !this._pageControls)} />
@@ -177,16 +174,22 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
}
+ _initialScale: number | undefined;
render() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive");
let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf;
- if (!noPdf && (this.props.isSelected() || this.props.ScreenToLocalTransform().Scale < 2.5)) this._everActive = true;
- return (!this._everActive ?
- <div className="pdfBox-title-outer"><div className="pdfBox-title-cont" >
- <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong> </div></div> :
+ if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale;
+ if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
+ return (noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ?
+ <div className="pdfBox-title-outer" >
+ <div className={classname} >
+ <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong>
+ </div>
+ </div> :
<div className={classname} style={{
- transformOrigin: "top left", width: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
+ transformOrigin: "top left",
+ width: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
height: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
transform: `scale(${this.props.Document.fitWidth ? this.props.ContentScaling() : 1})`
}} onContextMenu={this.specificContextMenu} onPointerDown={(e: React.PointerEvent) => {
@@ -199,11 +202,11 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
- addDocTab={this.props.addDocTab} GoToPage={this.gotoPage}
+ addDocTab={this.props.addDocTab} GoToPage={this.gotoPage} focus={this.props.focus}
pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
- fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this.props.ScreenToLocalTransform().Scale < 2.5 ? true : false} />
+ fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} />
{this.settingsPanel()}
</div>);
}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index f44ca99b9..15fafb022 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -43,8 +43,8 @@ export class PresBox extends React.Component<FieldViewProps> {
value.forEach((item, i) => {
if (item instanceof Doc && item.type !== DocumentType.PRESELEMENT) {
let pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
- Doc.GetProto(pinDoc).target = item;
- Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.target instanceof Doc) && this.target.title.toString()');
+ Doc.GetProto(pinDoc).presentationTargetDoc = item;
+ Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
value.splice(i, 1, pinDoc);
}
});
@@ -124,13 +124,13 @@ export class PresBox extends React.Component<FieldViewProps> {
this.childDocs.forEach((doc, ind) => {
//the order of cases is aligned based on priority
if (doc.hideTillShownButton && ind <= index) {
- (doc.target as Doc).opacity = 1;
+ (doc.presentationTargetDoc as Doc).opacity = 1;
}
if (doc.hideAfterButton && ind < index) {
- (doc.target as Doc).opacity = 0;
+ (doc.presentationTargetDoc as Doc).opacity = 0;
}
if (doc.fadeButton && ind < index) {
- (doc.target as Doc).opacity = 0.5;
+ (doc.presentationTargetDoc as Doc).opacity = 0.5;
}
});
}
@@ -145,13 +145,13 @@ export class PresBox extends React.Component<FieldViewProps> {
//the order of cases is aligned based on priority
if (key.hideAfterButton && ind >= index) {
- (key.target as Doc).opacity = 1;
+ (key.presentationTargetDoc as Doc).opacity = 1;
}
if (key.fadeButton && ind >= index) {
- (key.target as Doc).opacity = 1;
+ (key.presentationTargetDoc as Doc).opacity = 1;
}
if (key.hideTillShownButton && ind > index) {
- (key.target as Doc).opacity = 0;
+ (key.presentationTargetDoc as Doc).opacity = 0;
}
});
}
@@ -162,7 +162,7 @@ export class PresBox extends React.Component<FieldViewProps> {
* te option open, navigates to that element.
*/
navigateToElement = async (curDoc: Doc, fromDocIndex: number) => {
- let fromDoc = this.childDocs[fromDocIndex].target as Doc;
+ let fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc;
let docToJump = curDoc;
let willZoom = false;
@@ -190,7 +190,7 @@ export class PresBox extends React.Component<FieldViewProps> {
//docToJump stayed same meaning, it was not in the group or was the last element in the group
if (docToJump === curDoc) {
//checking if curDoc has navigation open
- let target = await curDoc.target as Doc;
+ let target = await curDoc.presentationTargetDoc as Doc;
if (curDoc.navButton) {
DocumentManager.Instance.jumpToDocument(target, false);
} else if (curDoc.showButton) {
@@ -210,8 +210,8 @@ export class PresBox extends React.Component<FieldViewProps> {
let curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
//awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(await docToJump.target as Doc, willZoom);
- let newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.target as Doc);
+ await DocumentManager.Instance.jumpToDocument(await docToJump.presentationTargetDoc as Doc, willZoom);
+ let newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc);
curDoc.viewScale = newScale;
//saving the scale that user was on
if (curScale !== 1) {
@@ -314,12 +314,11 @@ export class PresBox extends React.Component<FieldViewProps> {
}
toggleMinimize = undoBatch(action((e: React.PointerEvent) => {
- if (this.props.Document.minimizedView) {
- this.props.Document.minimizedView = false;
+ if (this.props.Document.inOverlay) {
Doc.RemoveDocFromList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document);
CollectionDockingView.AddRightSplit(this.props.Document, this.props.DataDoc);
+ this.props.Document.inOverlay = false;
} else {
- this.props.Document.minimizedView = true;
this.props.Document.x = e.clientX + 25;
this.props.Document.y = e.clientY - 25;
this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close");
@@ -370,9 +369,9 @@ export class PresBox extends React.Component<FieldViewProps> {
<FontAwesomeIcon icon={this.props.Document.presStatus ? "stop" : "play"} />
</button>
<button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
- <button className="presBox-button" title={this.props.Document.minimizedView ? "Expand" : "Minimize"} onClick={this.toggleMinimize}><FontAwesomeIcon icon={"eye"} /></button>
+ <button className="presBox-button" title={this.props.Document.inOverlay ? "Expand" : "Minimize"} onClick={this.toggleMinimize}><FontAwesomeIcon icon={"eye"} /></button>
</div>
- {this.props.Document.minimizedView ? (null) :
+ {this.props.Document.inOverlay ? (null) :
<div className="presBox-listCont" >
<CollectionView {...this.props} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
</div>
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index b7d9a1eab..e83aa8bea 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,7 +3,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction,
import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { InkTool } from "../../../new_fields/InkField";
-import { makeInterface } from "../../../new_fields/Schema";
+import { makeInterface, createSchema } from "../../../new_fields/Schema";
import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
import { RouteStore } from "../../../server/RouteStore";
@@ -16,16 +16,19 @@ import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { documentSchema } from "./DocumentView";
import { FieldView, FieldViewProps } from './FieldView';
-import { pageSchema } from "./ImageBox";
import "./VideoBox.scss";
import { library } from "@fortawesome/fontawesome-svg-core";
import { faVideo } from "@fortawesome/free-solid-svg-icons";
import { Doc } from "../../../new_fields/Doc";
import { ScriptField } from "../../../new_fields/ScriptField";
+import { positionSchema } from "./CollectionFreeFormDocumentView";
var path = require('path');
-type VideoDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>;
-const VideoDocument = makeInterface(documentSchema, pageSchema);
+export const timeSchema = createSchema({
+ currentTimecode: "number",
+});
+type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>;
+const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema);
library.add(faVideo);
@@ -97,11 +100,11 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
@action public Snapshot() {
- let width = NumCast(this.props.Document.width);
- let height = NumCast(this.props.Document.height);
+ let width = this.Document.width || 0;
+ let height = this.Document.height || 0;
var canvas = document.createElement('canvas');
canvas.width = 640;
- canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+ canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
if (ctx) {
ctx.rect(0, 0, canvas.width, canvas.height);
@@ -112,24 +115,25 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
if (!this._videoRef) { // can't find a way to take snapshots of videos
let b = Docs.Create.ButtonDocument({
- x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
- width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString()
+ x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+ width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString()
});
- b.onClick = ScriptField.MakeScript(`this.curPage = ${NumCast(this.props.Document.curPage)}`);
+ b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`);
} else {
//convert to desired file format
var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
- VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
+ let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString()));
+ VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => {
if (returnedFilename) {
let url = this.choosePath(Utils.prepend(returnedFilename));
let imageSummary = Docs.Create.ImageDocument(url, {
- x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
- width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
+ x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
+ width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
});
+ imageSummary.isButton = true;
this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false);
- DocUtils.MakeLink(imageSummary, this.props.Document);
+ DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot");
}
});
}
@@ -137,8 +141,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
@action
updateTimecode = () => {
- this.player && (this.props.Document.curPage = this.player.currentTime);
- this._youtubePlayer && (this.props.Document.curPage = this._youtubePlayer.getCurrentTime());
+ this.player && (this.Document.currentTimecode = this.player.currentTime);
+ this._youtubePlayer && (this.Document.currentTimecode = this._youtubePlayer.getCurrentTime());
}
componentDidMount() {
@@ -146,12 +150,12 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
if (this.youtubeVideoId) {
let youtubeaspect = 400 / 315;
- var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
- var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
+ var nativeWidth = (this.Document.nativeWidth || 0);
+ var nativeHeight = (this.Document.nativeHeight || 0);
if (!nativeWidth || !nativeHeight) {
if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect;
- this.Document.height = FieldValue(this.Document.width, 0) / youtubeaspect;
+ this.Document.height = (this.Document.width || 0) / youtubeaspect;
}
}
}
@@ -168,10 +172,9 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
if (vref) {
this._videoRef!.ontimeupdate = this.updateTimecode;
vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
- if (this._reactionDisposer) this._reactionDisposer();
- this._reactionDisposer = reaction(() => this.props.Document.curPage, () =>
- !this.Playing && (vref.currentTime = this.Document.curPage || 0)
- , { fireImmediately: true });
+ this._reactionDisposer && this._reactionDisposer();
+ this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0,
+ time => !this.Playing && (vref.currentTime = time), { fireImmediately: true });
}
}
@@ -242,7 +245,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let onYoutubePlayerReady = (event: any) => {
this._reactionDisposer && this._reactionDisposer();
this._youtubeReactionDisposer && this._youtubeReactionDisposer();
- this._reactionDisposer = reaction(() => this.props.Document.curPage, () => !this.Playing && this.Seek(this.Document.curPage || 0));
+ this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this.Playing && this.Seek(this.Document.currentTimecode || 0));
this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
iframe.style.pointerEvents = interactive ? "all" : "none";
@@ -263,9 +266,9 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
- let start = untracked(() => Math.round(NumCast(this.props.Document.curPage)));
+ let start = untracked(() => Math.round(this.Document.currentTimecode || 0));
return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
- onLoad={this.youtubeIframeLoaded} className={`${style}`} width={NumCast(this.props.Document.nativeWidth, 640)} height={NumCast(this.props.Document.nativeHeight, 390)}
+ onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`}
></iframe>;
}
diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss
index 0c6df74f0..cc326eb93 100644
--- a/src/client/views/pdf/Annotation.scss
+++ b/src/client/views/pdf/Annotation.scss
@@ -2,6 +2,5 @@
pointer-events: all;
user-select: none;
position: absolute;
- background-color: red;
- opacity: 0.1;
+ background-color: rgba(146, 245, 95, 0.467);
} \ No newline at end of file
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 3ed85f6a5..5a07b88d9 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,7 +1,7 @@
import React = require("react");
import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, WidthSym, Opt, DocListCastAsync } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
@@ -14,6 +14,7 @@ interface IAnnotationProps {
fieldExtensionDoc: Doc;
addDocTab: (document: Doc, dataDoc: Opt<Doc>, where: string) => boolean;
pinToPres: (document: Doc) => void;
+ focus: (doc: Doc) => void;
}
export default class Annotation extends React.Component<IAnnotationProps> {
@@ -50,16 +51,17 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
);
this._brushDisposer = reaction(
- () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.IsBrushed(FieldValue(Cast(this.props.document.group, Doc))!),
+ () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.isBrushedHighlightedDegree(FieldValue(Cast(this.props.document.group, Doc))!),
(brushed) => {
if (brushed !== undefined) {
- runInAction(() => this._brushed = brushed);
+ runInAction(() => this._brushed = brushed !== 0);
}
}
);
}
componentWillUnmount() {
+ this._brushDisposer && this._brushDisposer();
this._reactionDisposer && this._reactionDisposer();
}
@@ -92,20 +94,19 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
PDFMenu.Instance.AddTag = this.addTag.bind(this);
PDFMenu.Instance.PinToPres = this.pinToPres;
PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true);
+ e.stopPropagation();
}
else if (e.button === 0) {
- let targetDoc = await Cast(this.props.document.target, Doc);
- if (targetDoc) {
- let context = await Cast(targetDoc.targetContext, Doc);
- if (context) {
- DocumentManager.Instance.jumpToDocument(targetDoc, false, false,
- ((doc) => this.props.addDocTab(targetDoc!, undefined, e.ctrlKey ? "onRight" : "inTab")),
- undefined, undefined);
- }
+ let annoGroup = await Cast(this.props.document.group, Doc);
+ if (annoGroup) {
+ DocumentManager.Instance.FollowLink(undefined, annoGroup,
+ (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, undefined, e.ctrlKey ? "onRight" : "inTab"),
+ false, false, undefined);
}
}
}
+
addTag = (key: string, value: string): boolean => {
let group = FieldValue(Cast(this.props.document.group, Doc));
if (group) {
@@ -123,7 +124,9 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
left: this.props.x,
width: this.props.width,
height: this.props.height,
- backgroundColor: this._brushed ? "green" : StrCast(this.props.document.color)
+ opacity: this._brushed ? 0.5 : undefined,
+ backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor),
+ transition: "opacity 0.5s",
}} />);
}
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 2202351ee..517a99a68 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -4,7 +4,7 @@ import { observable, action, } from "mobx";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { emptyFunction, returnFalse } from "../../../Utils";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, Opt } from "../../../new_fields/Doc";
@observer
export default class PDFMenu extends React.Component {
@@ -31,7 +31,7 @@ export default class PDFMenu extends React.Component {
@observable public Pinned: boolean = false;
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction;
- public Highlight: (d: Doc | undefined, color: string) => void = emptyFunction;
+ public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
public Delete: () => void = emptyFunction;
public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction;
public AddTag: (key: string, value: string) => boolean = returnFalse;
@@ -155,12 +155,8 @@ export default class PDFMenu extends React.Component {
@action
highlightClicked = (e: React.MouseEvent) => {
- if (!this.Pinned) {
- this.Highlight(undefined, "#f4f442");
- }
- else {
+ if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { // yellowish highlight color for a marker type highlight
this.Highlighting = !this.Highlighting;
- this.Highlight(undefined, "#f4f442");
}
}
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 8027e93a3..f6fedf3da 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -11,10 +11,22 @@
// transform: scale(0.75);
// transform-origin: top left;
// }
- // .textLayer {
- // transform: scale(0.75);
- // transform-origin: top left;
- // }
+ .textLayer {
+
+ mix-blend-mode: multiply;
+ opacity: 0.9;
+ span {
+ padding-right: 5px;
+ padding-bottom: 4px;
+ }
+ }
+ .textLayer ::selection { background: yellow; } // should match the backgroundColor in createAnnotation()
+ .textLayer .highlight {
+ background-color: yellow;
+ }
+ .textLayer .highlight.selected {
+ background-color: orange;
+ }
.page {
position: relative;
@@ -30,7 +42,6 @@
}
.pdfViewer-overlay {
- transform: scale(2.14359);
transform-origin: left top;
position: absolute;
top: 0px;
@@ -43,11 +54,11 @@
top: 0;
width: 100%;
pointer-events: none;
+ mix-blend-mode: multiply;
- .pdfPage-annotationBox {
+ .pdfViewer-annotationBox {
position: absolute;
- background-color: red;
- opacity: 0.1;
+ background-color: rgba(245, 230, 95, 0.616);
}
}
.pdfViewer-waiting {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 91a8cbf9f..b010d16c8 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -9,8 +9,7 @@ import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
import { ScriptField } from "../../../new_fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import smoothScroll, { Utils, emptyFunction, returnOne } from "../../../Utils";
-import { DocServer } from "../../DocServer";
+import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { CompiledScript, CompileScript } from "../../util/Scripting";
@@ -24,6 +23,8 @@ import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import Annotation from "./Annotation";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
const pdfjsLib = require("pdfjs-dist");
@@ -43,6 +44,7 @@ interface IViewerProps {
select: (isCtrlPressed: boolean) => void;
startupLive: boolean;
renderDepth: number;
+ focus: (doc: Doc) => void;
isSelected: () => boolean;
loaded: (nw: number, nh: number, np: number) => void;
active: () => boolean;
@@ -61,6 +63,7 @@ interface IViewerProps {
*/
@observer
export class PDFViewer extends React.Component<IViewerProps> {
+ static _annotationStyle: any = addStyleSheet();
@observable private _pageSizes: { width: number, height: number }[] = [];
@observable private _annotations: Doc[] = [];
@observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
@@ -74,8 +77,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
@observable private _showWaiting = true;
@observable private _showCover = false;
@observable private _zoomed = 1;
+ @observable private _scrollTop = 0;
- public pdfViewer: any;
+ private _pdfViewer: any;
private _retries = 0; // number of times tried to create the PDF viewer
private _isChildActive = false;
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
@@ -84,6 +88,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _selectionReactionDisposer?: IReactionDisposer;
private _annotationReactionDisposer?: IReactionDisposer;
private _filterReactionDisposer?: IReactionDisposer;
+ private _searchReactionDisposer?: IReactionDisposer;
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _selectionText: string = "";
@@ -102,12 +107,25 @@ export class PDFViewer extends React.Component<IViewerProps> {
return this._annotations.filter(anno => this._script.run({ this: anno }, console.log, true).result);
}
+ _lastSearch: string = "";
componentDidMount = async () => {
// change the address to be the file address of the PNG version of each page
// file address of the pdf
this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`)));
runInAction(() => this._showWaiting = this._showCover = true);
- this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => this.setupPdfJsViewer(), { fireImmediately: this.props.startupLive });
+ this.props.startupLive && this.setupPdfJsViewer();
+ this._searchReactionDisposer = reaction(() => StrCast(this.props.Document.search_string), searchString => {
+ if (searchString) {
+ this.search(searchString, true);
+ this._lastSearch = searchString;
+ }
+ else {
+ setTimeout(() => this._lastSearch === "mxytzlaf" && this.search("mxytzlaf", true), 200); // bcz: how do we clear search highlights?
+ this._lastSearch && (this._lastSearch = "mxytzlaf");
+ }
+ }, { fireImmediately: true });
+
+ this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => (this.props.isSelected() && SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(), { fireImmediately: true });
this._reactionDisposer = reaction(
() => this.props.Document.scrollY,
(scrollY) => {
@@ -128,28 +146,22 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._annotationReactionDisposer && this._annotationReactionDisposer();
this._filterReactionDisposer && this._filterReactionDisposer();
this._selectionReactionDisposer && this._selectionReactionDisposer();
+ this._searchReactionDisposer && this._searchReactionDisposer();
document.removeEventListener("copy", this.copy);
}
copy = (e: ClipboardEvent) => {
if (this.props.active() && e.clipboardData) {
- e.clipboardData.setData("text/plain", this._selectionText);
- e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]);
- e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument(undefined, "#0390fc")[Id]);
+ let annoDoc = this.makeAnnotationDocument("rgba(3,144,152,0.3)"); // copied text markup color (blueish)
+ if (annoDoc) {
+ e.clipboardData.setData("text/plain", this._selectionText);
+ e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]);
+ e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]);
+ }
e.preventDefault();
}
}
- paste = (e: ClipboardEvent) => {
- if (e.clipboardData && e.clipboardData.getData("dash/pdfOrigin") === this.props.Document[Id]) {
- let linkDocId = e.clipboardData.getData("dash/linkDoc");
- linkDocId && DocServer.GetRefField(linkDocId).then(async (link) =>
- (link instanceof Doc) && (Doc.GetProto(link).anchor2 = this.makeAnnotationDocument(await Cast(Doc.GetProto(link), Doc), "#0390fc", false)));
- }
- }
-
- setSelectionText = (text: string) => this._selectionText = text;
-
@action
initialLoad = async () => {
if (this._pageSizes.length === 0) {
@@ -193,13 +205,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
{ fireImmediately: true }
);
- document.removeEventListener("copy", this.copy);
- document.addEventListener("copy", this.copy);
- document.addEventListener("pagesinit", action(() => {
- this.pdfViewer.currentScaleValue = this._zoomed = 1;
- this.gotoPage(NumCast(this.props.Document.curPage, 1));
- }));
- document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false));
this.createPdfViewer();
}
@@ -211,41 +216,47 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
return;
}
+ document.removeEventListener("copy", this.copy);
+ document.addEventListener("copy", this.copy);
+ document.addEventListener("pagesinit", action(() => {
+ this._pdfViewer.currentScaleValue = this._zoomed = 1;
+ this.gotoPage(NumCast(this.props.Document.curPage, 1));
+ }));
+ document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false));
var pdfLinkService = new PDFJSViewer.PDFLinkService();
let pdfFindController = new PDFJSViewer.PDFFindController({
linkService: pdfLinkService,
});
- this.pdfViewer = new PDFJSViewer.PDFViewer({
+ this._pdfViewer = new PDFJSViewer.PDFViewer({
container: this._mainCont.current,
viewer: this._viewer.current,
linkService: pdfLinkService,
findController: pdfFindController,
renderer: "canvas",
});
- pdfLinkService.setViewer(this.pdfViewer);
+ pdfLinkService.setViewer(this._pdfViewer);
pdfLinkService.setDocument(this.props.pdf, null);
- this.pdfViewer.setDocument(this.props.pdf);
+ this._pdfViewer.setDocument(this.props.pdf);
}
+ @undoBatch
@action
- makeAnnotationDocument = (sourceDoc: Doc | undefined, color: string, createLink: boolean = true): Doc => {
+ makeAnnotationDocument = (color: string): Opt<Doc> => {
+ if (this._savedAnnotations.size() === 0) return undefined;
let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {});
let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
let annoDocs: Doc[] = [];
let minY = Number.MAX_VALUE;
- if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1 && !createLink) {
+ if ((this._savedAnnotations.values()[0][0] as any).marqueeing) {
let anno = this._savedAnnotations.values()[0][0];
- let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: "rgba(255, 0, 0, 0.1)", title: "Annotation on " + StrCast(this.props.Document.title) });
+ let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, title: "Annotation on " + StrCast(this.props.Document.title) });
if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
- annoDoc.target = sourceDoc;
annoDoc.group = mainAnnoDoc;
- annoDoc.color = color;
- annoDoc.type = AnnotationTypes.Region;
- annoDocs.push(annoDoc);
annoDoc.isButton = true;
+ annoDocs.push(annoDoc);
anno.remove();
mainAnnoDoc = annoDoc;
mainAnnoDocProto = Doc.GetProto(mainAnnoDoc);
@@ -257,10 +268,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
if (anno.style.top) annoDoc.y = parseInt(anno.style.top);
if (anno.style.height) annoDoc.height = parseInt(anno.style.height);
if (anno.style.width) annoDoc.width = parseInt(anno.style.width);
- annoDoc.target = sourceDoc;
annoDoc.group = mainAnnoDoc;
- annoDoc.color = color;
- annoDoc.type = AnnotationTypes.Region;
+ annoDoc.backgroundColor = color;
annoDocs.push(annoDoc);
anno.remove();
(annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY));
@@ -271,9 +280,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title);
mainAnnoDocProto.annotationOn = this.props.Document;
- if (sourceDoc && createLink) {
- DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`);
- }
this._savedAnnotations.clear();
this.Index = -1;
return mainAnnoDoc;
@@ -304,36 +310,21 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
gotoPage = (p: number) => {
- this.pdfViewer && this.pdfViewer.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
+ this._pdfViewer && this._pdfViewer.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) });
}
@action
scrollToAnnotation = (scrollToAnnotation: Doc) => {
- this.allAnnotations.forEach(d => Doc.UnBrushDoc(d));
- let windowHgt = this.props.PanelHeight() / this.props.ContentScaling();
- let scrollRange = this._mainCont.current!.scrollHeight - windowHgt;
- let pgScroll = scrollRange / this._pageSizes.length;
- this._mainCont.current!.scrollTo(0, NumCast(scrollToAnnotation.y) - pgScroll / 2);
- Doc.BrushDoc(scrollToAnnotation);
+ let offset = this.visibleHeight() / 2 * 96 / 72;
+ this._mainCont.current && smoothScroll(500, this._mainCont.current, NumCast(scrollToAnnotation.y) - offset);
+ Doc.linkFollowHighlight(scrollToAnnotation);
}
- sendAnnotations = (page: number) => {
- return this._savedAnnotations.getValue(page);
- }
-
- receiveAnnotations = (annotations: HTMLDivElement[], page: number) => {
- if (page === -1) {
- this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
- this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, annotations));
- }
- else {
- this._savedAnnotations.setValue(page, annotations);
- }
- }
@action
onScroll = (e: React.UIEvent<HTMLElement>) => {
- this.pdfViewer && (this.props.Document.curPage = this.pdfViewer.currentPageNumber);
+ this._scrollTop = this._mainCont.current!.scrollTop;
+ this._pdfViewer && (this.props.Document.curPage = this._pdfViewer.currentPageNumber);
}
// get the page index that the vertical offset passed in is on
@@ -353,6 +344,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString();
}
this._annotationLayer.current.append(div);
+ div.style.backgroundColor = "yellow";
+ div.style.opacity = "0.5";
let savedPage = this._savedAnnotations.getValue(page);
if (savedPage) {
savedPage.push(div);
@@ -369,8 +362,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
if (!searchString) {
fwd ? this.nextAnnotation() : this.prevAnnotation();
}
- else if (this.pdfViewer._pageViewsReady) {
- this.pdfViewer.findController.executeCommand('findagain', {
+ else if (this._pdfViewer._pageViewsReady) {
+ this._pdfViewer.findController.executeCommand('findagain', {
caseSensitive: false,
findPrevious: !fwd,
highlightAll: true,
@@ -380,7 +373,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
else if (this._mainCont.current) {
let executeFind = () => {
- this.pdfViewer.findController.executeCommand('find', {
+ this._pdfViewer.findController.executeCommand('find', {
caseSensitive: false,
findPrevious: !fwd,
highlightAll: true,
@@ -398,36 +391,31 @@ export class PDFViewer extends React.Component<IViewerProps> {
// if alt+left click, drag and annotate
this._downX = e.clientX;
this._downY = e.clientY;
+ addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
if (NumCast(this.props.Document.scale, 1) !== 1) return;
if ((e.button !== 0 || e.altKey) && this.active()) {
this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
}
this._marqueeing = false;
if (!e.altKey && e.button === 0 && this.active()) {
+ // clear out old marquees and initialize menu for new selection
PDFMenu.Instance.StartDrag = this.startDrag;
PDFMenu.Instance.Highlight = this.highlight;
PDFMenu.Instance.Snippet = this.createSnippet;
PDFMenu.Instance.Status = "pdf";
PDFMenu.Instance.fadeOut(true);
+ this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
if (e.target && (e.target as any).parentElement.className === "textLayer") {
- if (!e.ctrlKey) {
- this.receiveAnnotations([], -1);
- }
+ // start selecting text if mouse down on textLayer spans
}
- else {
+ else if (this._mainCont.current) {
// set marquee x and y positions to the spatially transformed position
- if (this._mainCont.current) {
- let boundingRect = this._mainCont.current.getBoundingClientRect();
- this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width);
- this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop;
- }
+ let boundingRect = this._mainCont.current.getBoundingClientRect();
+ this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width);
+ this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop;
+ this._marqueeHeight = this._marqueeWidth = 0;
this._marqueeing = true;
- let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox");
- if (marquees && marquees.length) { // make a copy of the marquee
- let marquee = marquees[0] as HTMLDivElement;
- marquee.style.opacity = "0.2";
- }
- this.receiveAnnotations([], -1);
}
document.removeEventListener("pointermove", this.onSelectMove);
document.addEventListener("pointermove", this.onSelectMove);
@@ -462,12 +450,13 @@ export class PDFViewer extends React.Component<IViewerProps> {
let clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
let rect = clientRects.item(i);
- if (rect/* && rect.width !== this._mainCont.current.getBoundingClientRect().width && rect.height !== this._mainCont.current.getBoundingClientRect().height / this.props.pdf.numPages*/) {
+ if (rect) {
let scaleY = this._mainCont.current.offsetHeight / boundingRect.height;
let scaleX = this._mainCont.current.offsetWidth / boundingRect.width;
- if (rect.width !== this._mainCont.current.clientWidth) {
+ if (rect.width !== this._mainCont.current.clientWidth &&
+ (i === 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) {
let annoBox = document.createElement("div");
- annoBox.className = "pdfPage-annotationBox";
+ annoBox.className = "pdfViewer-annotationBox";
// transforms the positions from screen onto the pdf div
annoBox.style.top = ((rect.top - boundingRect.top) * scaleY + this._mainCont.current.scrollTop).toString();
annoBox.style.left = ((rect.left - boundingRect.left) * scaleX).toString();
@@ -478,8 +467,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
}
}
- let text = selRange.cloneContents().textContent;
- text && this.setSelectionText(text);
+ this._selectionText = selRange.cloneContents().textContent || "";
// clear selection
if (sel.empty) { // Chrome
@@ -491,22 +479,23 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action
onSelectEnd = (e: PointerEvent): void => {
+ clearStyleSheetRules(PDFViewer._annotationStyle);
+ this._savedAnnotations.clear();
if (this._marqueeing) {
if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox");
- if (marquees && marquees.length) { // make a copy of the marquee
+ if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation.
+ let style = (marquees[0] as HTMLDivElement).style;
let copy = document.createElement("div");
- let marquee = marquees[0] as HTMLDivElement;
- let style = marquee.style;
copy.style.left = style.left;
copy.style.top = style.top;
copy.style.width = style.width;
copy.style.height = style.height;
copy.style.border = style.border;
copy.style.opacity = style.opacity;
- copy.className = "pdfPage-annotationBox";
+ (copy as any).marqueeing = true;
+ copy.className = "pdfViewer-annotationBox";
this.createAnnotation(copy, this.getPageFromScroll(this._marqueeY));
- marquee.style.opacity = "0";
}
if (!e.ctrlKey) {
@@ -515,8 +504,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
}
-
- this._marqueeHeight = this._marqueeWidth = 0;
+ this._marqueeing = false;
}
else {
let sel = window.getSelection();
@@ -527,8 +515,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
}
- if (PDFMenu.Instance.Highlighting) {
- this.highlight(undefined, "goldenrod");
+ if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up
+ this.highlight("rgba(245, 230, 95, 0.616)"); // yellowish highlight color for highlighted text (should match PDFMenu's highlight color)
}
else {
PDFMenu.Instance.StartDrag = this.startDrag;
@@ -539,10 +527,10 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
@action
- highlight = (targetDoc: Doc | undefined, color: string) => {
+ highlight = (color: string) => {
// creates annotation documents for current highlights
- let annotationDoc = this.makeAnnotationDocument(targetDoc, color, false);
- Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc);
+ let annotationDoc = this.makeAnnotationDocument(color);
+ annotationDoc && Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc);
return annotationDoc;
}
@@ -554,23 +542,19 @@ export class PDFViewer extends React.Component<IViewerProps> {
startDrag = (e: PointerEvent, ele: HTMLElement): void => {
e.preventDefault();
e.stopPropagation();
- let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "New Annotation" });
- targetDoc.targetPage = this.getPageFromScroll(this._marqueeY);
- let annotationDoc = this.highlight(undefined, "red");
- annotationDoc.linkedToDoc = false;
- let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc);
- DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
- handlers: {
- dragComplete: () => {
- if (!annotationDoc.linkedToDoc) {
- let annotations = DocListCast(annotationDoc.annotations);
- annotations && annotations.forEach(anno => anno.target = targetDoc);
- DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`);
- }
- }
- },
- hideSource: false
- });
+ let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title });
+ const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
+ if (annotationDoc) {
+ let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc);
+ DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
+ handlers: {
+ dragComplete: () => !(dragData as any).linkedToDoc &&
+ DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${StrCast(this.props.Document.title)}`, "link from PDF")
+
+ },
+ hideSource: false
+ });
+ }
}
createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => {
@@ -601,6 +585,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
@action.bound
removeDocument(doc: Doc): boolean {
+ Doc.GetProto(doc).annotationOn = undefined;
//TODO This won't create the field if it doesn't already exist
let targetDataDoc = this.props.fieldExtensionDoc;
let targetField = this.props.fieldExt;
@@ -611,7 +596,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
return true;
}
scrollXf = () => {
- return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._mainCont.current.scrollTop) : this.props.ScreenToLocalTransform();
+ return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform();
}
setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => {
this._setPreviewCursor = func;
@@ -632,11 +617,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
getCoverImage = () => {
- if (!this.props.Document[HeightSym]()) {
- setTimeout(() => {
+ if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) {
+ setTimeout((() => {
this.props.Document.height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width;
this.props.Document.nativeHeight = nativeWidth * this._coverPath.height / this._coverPath.width;
- }, 0);
+ }).bind(this), 0);
}
let nativeWidth = NumCast(this.props.Document.nativeWidth);
let nativeHeight = NumCast(this.props.Document.nativeHeight);
@@ -649,43 +634,23 @@ export class PDFViewer extends React.Component<IViewerProps> {
onZoomWheel = (e: React.WheelEvent) => {
e.stopPropagation();
if (e.ctrlKey) {
- let curScale = Number(this.pdfViewer.currentScaleValue);
- this.pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale + curScale * e.deltaY / 1000));
- this._zoomed = Number(this.pdfViewer.currentScaleValue);
+ let curScale = Number(this._pdfViewer.currentScaleValue);
+ this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale + curScale * e.deltaY / 1000));
+ this._zoomed = Number(this._pdfViewer.currentScaleValue);
}
}
@computed get annotationLayer() {
return <div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
{this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
- <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
- </div>;
- }
- @computed get pdfViewerDiv() {
- return <div className="pdfViewer-text" ref={this._viewer} style={{ transformOrigin: "left top" }} />;
- }
- @computed get standinViews() {
- return <>
- {this._showCover ? this.getCoverImage() : (null)}
- {this._showWaiting ? <img className="pdfViewer-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
- </>;
- }
- marqueeWidth = () => this._marqueeWidth;
- marqueeHeight = () => this._marqueeHeight;
- marqueeX = () => this._marqueeX;
- marqueeY = () => this._marqueeY;
- marqueeing = () => this._marqueeing;
- render() {
- return (<div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} ref={this._mainCont}>
- {this.pdfViewerDiv}
- <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
- <div className="pdfViewer-overlay" style={{ transform: `scale(${this._zoomed})` }}>
- {this.annotationLayer}
+ <Annotation {...this.props} focus={this.props.focus} anno={anno} key={`${anno[Id]}-annotation`} />)}
+ <div className="pdfViewer-overlay" id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
<CollectionFreeFormView {...this.props}
setPreviewCursor={this.setPreviewCursor}
PanelHeight={() => NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))}
PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : NumCast(this.props.Document.nativeWidth)}
- focus={emptyFunction}
+ VisibleHeight={this.visibleHeight}
+ focus={this.props.focus}
isSelected={this.props.isSelected}
select={emptyFunction}
active={this.active}
@@ -693,7 +658,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
whenActiveChanged={this.whenActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
- addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }}
+ addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }}
CollectionView={this.props.ContainingCollectionView}
ScreenToLocalTransform={this.scrollXf}
ruleProvider={undefined}
@@ -702,7 +667,29 @@ export class PDFViewer extends React.Component<IViewerProps> {
chromeCollapsed={true}>
</CollectionFreeFormView>
</div>
+ </div>;
+ }
+ @computed get pdfViewerDiv() {
+ return <div className="pdfViewer-text" ref={this._viewer} style={{ transformOrigin: "left top" }} />;
+ }
+ @computed get standinViews() {
+ return <>
+ {this._showCover ? this.getCoverImage() : (null)}
+ {this._showWaiting ? <img className="pdfViewer-waiting" key="waiting" src={"/assets/loading.gif"} /> : (null)}
+ </>;
+ }
+ marqueeWidth = () => this._marqueeWidth;
+ marqueeHeight = () => this._marqueeHeight;
+ marqueeX = () => this._marqueeX;
+ marqueeY = () => this._marqueeY;
+ marqueeing = () => this._marqueeing;
+ visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96;
+ render() {
+ return (<div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} ref={this._mainCont}>
+ {this.pdfViewerDiv}
+ {this.annotationLayer}
{this.standinViews}
+ <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
</div >);
}
}
@@ -722,11 +709,9 @@ class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
style={{
left: `${this.props.x()}px`, top: `${this.props.y()}px`,
width: `${this.props.width()}px`, height: `${this.props.height()}px`,
- border: `${this.props.width() === 0 ? "" : "2px dashed black"}`
+ border: `${this.props.width() === 0 ? "" : "2px dashed black"}`,
+ opacity: 0.2
}}>
</div>;
}
-}
-
-
-export enum AnnotationTypes { Region }
+} \ No newline at end of file
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index de3242d32..daf000dc7 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -63,13 +63,11 @@ export class PresElementBox extends React.Component<FieldViewProps> {
this.hideTillShownButton = !this.hideTillShownButton;
if (!this.hideTillShownButton) {
if (this.myIndex >= this.currentIndex) {
- (this.props.Document.target as Doc).opacity = 1;
+ (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
}
} else {
- if (this.presentationDoc.presStatus) {
- if (this.myIndex > this.currentIndex) {
- (this.props.Document.target as Doc).opacity = 0;
- }
+ if (this.presentationDoc.presStatus && this.myIndex > this.currentIndex) {
+ (this.props.Document.presentationTargetDoc as Doc).opacity = 0;
}
}
}
@@ -85,14 +83,12 @@ export class PresElementBox extends React.Component<FieldViewProps> {
this.hideAfterButton = !this.hideAfterButton;
if (!this.hideAfterButton) {
if (this.myIndex <= this.currentIndex) {
- (this.props.Document.target as Doc).opacity = 1;
+ (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
}
} else {
if (this.fadeButton) this.fadeButton = false;
- if (this.presentationDoc.presStatus) {
- if (this.myIndex < this.currentIndex) {
- (this.props.Document.target as Doc).opacity = 0;
- }
+ if (this.presentationDoc.presStatus && this.myIndex < this.currentIndex) {
+ (this.props.Document.presentationTargetDoc as Doc).opacity = 0;
}
}
}
@@ -108,14 +104,12 @@ export class PresElementBox extends React.Component<FieldViewProps> {
this.fadeButton = !this.fadeButton;
if (!this.fadeButton) {
if (this.myIndex <= this.currentIndex) {
- (this.props.Document.target as Doc).opacity = 1;
+ (this.props.Document.presentationTargetDoc as Doc).opacity = 1;
}
} else {
this.hideAfterButton = false;
- if (this.presentationDoc.presStatus) {
- if (this.myIndex < this.currentIndex) {
- (this.props.Document.target as Doc).opacity = 0.5;
- }
+ if (this.presentationDoc.presStatus && (this.myIndex < this.currentIndex)) {
+ (this.props.Document.presentationTargetDoc as Doc).opacity = 0.5;
}
}
}
@@ -162,7 +156,7 @@ export class PresElementBox extends React.Component<FieldViewProps> {
* presentation element.
*/
renderEmbeddedInline = () => {
- if (!this.embedOpen || !(this.props.Document.target instanceof Doc)) {
+ if (!this.embedOpen || !(this.props.Document.presentationTargetDoc instanceof Doc)) {
return (null);
}
@@ -175,8 +169,8 @@ export class PresElementBox extends React.Component<FieldViewProps> {
width: propDocWidth === 0 ? "auto" : propDocWidth * scale(),
}}>
<CollectionSchemaPreview
- fitToBox={StrCast(this.props.Document.target.type).indexOf(DocumentType.COL) !== -1}
- Document={this.props.Document.target}
+ fitToBox={StrCast(this.props.Document.presentationTargetDoc.type).indexOf(DocumentType.COL) !== -1}
+ Document={this.props.Document.presentationTargetDoc}
addDocument={returnFalse}
removeDocument={returnFalse}
ruleProvider={undefined}
@@ -205,7 +199,7 @@ export class PresElementBox extends React.Component<FieldViewProps> {
let pbi = "presElementBox-interaction";
return (
<div className={className} key={p.Document[Id] + this.myIndex}
- style={{ outlineWidth: Doc.IsBrushed(p.Document.target as Doc) ? `1px` : "0px", }}
+ style={{ outlineWidth: Doc.IsBrushed(p.Document.presentationTargetDoc as Doc) ? `1px` : "0px", }}
onClick={e => { p.focus(p.Document); e.stopPropagation(); }}>
{treecontainer ? (null) : <>
<strong className="presElementBox-name">
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 5ed33a596..0dd4d3dc5 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -34,6 +34,9 @@
&.searchBox-filter {
align-self: stretch;
+ }
+
+ &.searchBox-submit {
margin-left: 2px;
margin-right: 2px
}
@@ -45,6 +48,12 @@
}
}
+.searchBox-quickFilter {
+ width: 500px;
+ margin-left: 25px;
+ margin-top: 10px;
+}
+
.searchBox-results {
margin-right: 136px;
top: 300px;
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index f53270c64..be75a29e0 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -18,6 +18,7 @@ import { FilterBox } from './FilterBox';
import "./FilterBox.scss";
import "./SearchBox.scss";
import { SearchItem } from './SearchItem';
+import { IconBar } from './IconBar';
import { string } from 'prop-types';
library.add(faTimes);
@@ -236,7 +237,7 @@ export class SearchBox extends React.Component {
}
@action.bound
- openSearch(e: React.PointerEvent) {
+ openSearch(e: React.SyntheticEvent) {
e.stopPropagation();
this._openNoResults = false;
FilterBox.Instance.closeFilter();
@@ -342,12 +343,16 @@ export class SearchBox extends React.Component {
<FontAwesomeIcon icon="object-group" size="lg" />
</span>
<input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef}
- className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter}
+ className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch}
style={{ width: this._searchbarOpen ? "500px" : "100px" }} />
+ <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button>
<button className="searchBox-barChild searchBox-submit" onClick={this.submitSearch} onPointerDown={FilterBox.Instance.stopProp}>Submit</button>
- <button className="searchBox-barChild searchBox-filter" onClick={FilterBox.Instance.openFilter} onPointerDown={FilterBox.Instance.stopProp}>Filter</button>
<button className="searchBox-barChild searchBox-close" title={"Close Search Bar"} onPointerDown={MainView.Instance.toggleSearch}><FontAwesomeIcon icon={faTimes} size="lg" /></button>
</div>
+ {(this._numTotalResults > 0 || !this._searchbarOpen) ? (null) :
+ (<div className="searchBox-quickFilter" onPointerDown={this.openSearch}>
+ <div className="filter-panel"><IconBar /></div>
+ </div>)}
<div className="searchBox-results" onScroll={this.resultsScrolled} style={{
display: this._resultsOpen ? "flex" : "none",
height: this.resFull ? "560px" : this.resultHeight, overflow: this.resFull ? "auto" : "visible"
diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss
index 273d49349..62715c5eb 100644
--- a/src/client/views/search/SearchItem.scss
+++ b/src/client/views/search/SearchItem.scss
@@ -4,7 +4,6 @@
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
- height: 70px;
z-index: 0;
}
@@ -15,9 +14,12 @@
border-color: $intermediate-color;
border-bottom-style: solid;
padding: 10px;
- height: 70px;
+ min-height: 70px;
+ max-height: 150px;
+ height: auto;
z-index: 0;
display: inline-block;
+ overflow: auto;
.main-search-info {
display: flex;
@@ -26,6 +28,7 @@
.search-title-container {
width: 100%;
+ overflow: hidden;
.search-title {
text-transform: uppercase;
@@ -181,6 +184,12 @@
background: $lighter-alt-accent;
}
+.search-highlighting {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: pre;
+}
+
.searchBox-instances {
float: left;
opacity: 1;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 4d021216d..a7822ed46 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -4,13 +4,10 @@ import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlob
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { Doc } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import { ObjectField } from "../../../new_fields/ObjectField";
-import { RichTextField } from "../../../new_fields/RichTextField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../../Utils";
-import { DocServer } from "../../DocServer";
import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, SetupDrag } from "../../util/DragManager";
@@ -228,6 +225,12 @@ export class SearchItem extends React.Component<SearchItemProps> {
@action
pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); }
+ nextHighlight = (e: React.PointerEvent) => {
+ e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e);
+ let sstring = StrCast(this.props.doc.search_string);
+ this.props.doc.search_string = "";
+ setTimeout(() => this.props.doc.search_string = sstring, 0);
+ }
highlightDoc = (e: React.PointerEvent) => {
if (this.props.doc.type === DocumentType.LINK) {
if (this.props.doc.anchor1 && this.props.doc.anchor2) {
@@ -240,6 +243,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
} else {
Doc.BrushDoc(this.props.doc);
}
+ e.stopPropagation();
}
unHighlightDoc = (e: React.PointerEvent) => {
@@ -283,13 +287,14 @@ export class SearchItem extends React.Component<SearchItemProps> {
const doc2 = Cast(this.props.doc.anchor2, Doc);
return (
<div className="search-overview" onPointerDown={this.pointerDown} onContextMenu={this.onContextMenu}>
- <div className="search-item" onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} id="result"
- onClick={this.onClick} onPointerDown={this.pointerDown} >
+ <div className="search-item" onPointerDown={this.nextHighlight} onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} id="result"
+ onClick={this.onClick}>
<div className="main-search-info">
<div title="Drag as document" onPointerDown={this.onPointerDown} style={{ marginRight: "7px" }}> <FontAwesomeIcon icon="file" size="lg" /> </div>
<div className="search-title-container">
<div className="search-title">{StrCast(this.props.doc.title)}</div>
- <div className="search-highlighting">{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? "Text:" + this.props.lines[0] : ""}</div>
+ <div className="search-highlighting">{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}</div>
+ {this.props.lines.filter((m, i) => i).map((l, i) => <div id={i.toString()} className="search-highlighting">`${l}`</div>)}
</div>
<div className="search-info" style={{ width: this._useIcons ? "15%" : "400px" }}>
<div className={`icon-${this._useIcons ? "icons" : "live"}`}>
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index a68692732..418863bcc 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -63,6 +63,10 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) {
return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue);
}
+export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
+ return Cast(field, Doc);
+}
+
export function DocListCast(field: FieldResult): Doc[] {
return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
}
@@ -144,14 +148,8 @@ export class Doc extends RefField {
private [Self] = this;
private [SelfProxy]: any;
- public [WidthSym] = () => {
- let animDims = this[SelfProxy].animateToDimensions ? Array.from(Cast(this[SelfProxy].animateToDimensions, listSpec("number"))!) : undefined;
- return animDims ? animDims[0] : NumCast(this[SelfProxy].width);
- }
- public [HeightSym] = () => {
- let animDims = this[SelfProxy].animateToDimensions ? Array.from(Cast(this[SelfProxy].animateToDimensions, listSpec("number"))!) : undefined;
- return animDims ? animDims[1] : NumCast(this[SelfProxy].height);
- }
+ public [WidthSym] = () => NumCast(this[SelfProxy].width);
+ public [HeightSym] = () => NumCast(this[SelfProxy].height);
[ToScriptString]() {
return "invalid";
@@ -643,7 +641,7 @@ export namespace Doc {
export function isBrushedHighlightedDegree(doc: Doc) {
if (Doc.IsHighlighted(doc)) {
- return 3;
+ return 6;
}
else {
return Doc.IsBrushedDegree(doc);
@@ -671,10 +669,27 @@ export namespace Doc {
export function BrushDoc(doc: Doc) {
brushManager.BrushedDoc.set(doc, true);
brushManager.BrushedDoc.set(Doc.GetDataDoc(doc), true);
+ return doc;
}
export function UnBrushDoc(doc: Doc) {
brushManager.BrushedDoc.delete(doc);
brushManager.BrushedDoc.delete(Doc.GetDataDoc(doc));
+ return doc;
+ }
+
+ export function linkFollowUnhighlight() {
+ Doc.UnhighlightAll();
+ document.removeEventListener("pointerdown", linkFollowUnhighlight);
+ }
+
+ let dt = 0;
+ export function linkFollowHighlight(destDoc: Doc) {
+ linkFollowUnhighlight();
+ Doc.HighlightDoc(destDoc);
+ document.removeEventListener("pointerdown", linkFollowUnhighlight);
+ document.addEventListener("pointerdown", linkFollowUnhighlight);
+ let x = dt = Date.now();
+ window.setTimeout(() => dt === x && linkFollowUnhighlight(), 5000);
}
export class HighlightBrush {
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 8f64c1c2e..e381d0218 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -16,7 +16,7 @@ export interface StrokeData {
color: string;
width: string;
tool: InkTool;
- page: number;
+ displayTimecode: number;
}
export type InkData = Map<string, StrokeData>;
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 619cba3c6..46d897339 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -24,7 +24,7 @@ export namespace DashUploadUtils {
const gifs = [".gif"];
const pngs = [".png"];
const jpgs = [".jpg", ".jpeg"];
- const imageFormats = [...pngs, ...jpgs, ...gifs];
+ export const imageFormats = [...pngs, ...jpgs, ...gifs];
const videoFormats = [".mov", ".mp4"];
const size = "content-length";
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
index 8a9a6baae..b2a1af8d3 100644
--- a/src/server/RouteStore.ts
+++ b/src/server/RouteStore.ts
@@ -34,7 +34,8 @@ export enum RouteStore {
// APIS
cognitiveServices = "/cognitiveservices",
googleDocs = "/googleDocs",
- googlePhotosAccessToken = "/googlePhotosAccessToken",
+ readGooglePhotosAccessToken = "/readGooglePhotosAccessToken",
+ writeGooglePhotosAccessToken = "/writeGooglePhotosAccessToken",
googlePhotosMediaUpload = "/googlePhotosMediaUpload",
googlePhotosMediaDownload = "/googlePhotosMediaDownload",
googleDocsGet = "/googleDocsGet"
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index c899c2ef2..963c7736a 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -75,6 +75,42 @@ export namespace GoogleApiServerUtils {
});
};
+ const RetrieveOAuthClient = async (information: CredentialInformation) => {
+ return new Promise<OAuth2Client>((resolve, reject) => {
+ readFile(information.credentialsPath, async (err, credentials) => {
+ if (err) {
+ reject(err);
+ return console.log('Error loading client secret file:', err);
+ }
+ const { client_secret, client_id, redirect_uris } = parseBuffer(credentials).installed;
+ resolve(new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]));
+ });
+ });
+ };
+
+ export const GenerateAuthenticationUrl = async (information: CredentialInformation) => {
+ const client = await RetrieveOAuthClient(information);
+ return client.generateAuthUrl({
+ access_type: 'offline',
+ scope: SCOPES.map(relative => prefix + relative),
+ });
+ };
+
+ export const ProcessClientSideCode = async (information: CredentialInformation, authenticationCode: string): Promise<TokenResult> => {
+ const oAuth2Client = await RetrieveOAuthClient(information);
+ return new Promise<TokenResult>((resolve, reject) => {
+ oAuth2Client.getToken(authenticationCode, async (err, token) => {
+ if (err || !token) {
+ reject(err);
+ return console.error('Error retrieving access token', err);
+ }
+ oAuth2Client.setCredentials(token);
+ await Database.Auxiliary.GoogleAuthenticationToken.Write(information.userId, token);
+ resolve({ token, client: oAuth2Client });
+ });
+ });
+ };
+
export const RetrieveCredentials = (information: CredentialInformation) => {
return new Promise<TokenResult>((resolve, reject) => {
readFile(information.credentialsPath, async (err, credentials) => {
@@ -107,17 +143,13 @@ export namespace GoogleApiServerUtils {
return new Promise<TokenResult>((resolve, reject) => {
// Attempting to authorize user (${userId})
Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId).then(token => {
- if (!token) {
- // No token registered, so awaiting input from user
- return getNewToken(oAuth2Client, userId).then(resolve, reject);
- }
- if (token.expiry_date! < new Date().getTime()) {
+ if (token!.expiry_date! < new Date().getTime()) {
// Token has expired, so submitting a request for a refreshed access token
- return refreshToken(token, client_id, client_secret, oAuth2Client, userId).then(resolve, reject);
+ return refreshToken(token!, client_id, client_secret, oAuth2Client, userId).then(resolve, reject);
}
// Authentication successful!
- oAuth2Client.setCredentials(token);
- resolve({ token, client: oAuth2Client });
+ oAuth2Client.setCredentials(token!);
+ resolve({ token: token!, client: oAuth2Client });
});
});
}
@@ -145,35 +177,4 @@ export namespace GoogleApiServerUtils {
});
};
- /**
- * Get and store new token after prompting for user authorization, and then
- * execute the given callback with the authorized OAuth2 client.
- * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
- * @param {getEventsCallback} callback The callback for the authorized client.
- */
- function getNewToken(oAuth2Client: OAuth2Client, userId: string) {
- return new Promise<TokenResult>((resolve, reject) => {
- const authUrl = oAuth2Client.generateAuthUrl({
- access_type: 'offline',
- scope: SCOPES.map(relative => prefix + relative),
- });
- console.log('Authorize this app by visiting this url:', authUrl);
- const rl = createInterface({
- input: process.stdin,
- output: process.stdout,
- });
- rl.question('Enter the code from that page here: ', (code) => {
- rl.close();
- oAuth2Client.getToken(code, async (err, token) => {
- if (err || !token) {
- reject(err);
- return console.error('Error retrieving access token', err);
- }
- oAuth2Client.setCredentials(token);
- await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, token);
- resolve({ token, client: oAuth2Client });
- });
- });
- });
- }
} \ No newline at end of file
diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts
index 16c4f6c3a..4a67e57cc 100644
--- a/src/server/apis/google/GooglePhotosUploadUtils.ts
+++ b/src/server/apis/google/GooglePhotosUploadUtils.ts
@@ -4,6 +4,7 @@ import * as path from 'path';
import { MediaItemCreationResult } from './SharedTypes';
import { NewMediaItem } from "../../index";
import { BatchedArray, TimeUnit } from 'array-batcher';
+import { DashUploadUtils } from '../../DashUploadUtils';
export namespace GooglePhotosUploadUtils {
@@ -32,6 +33,9 @@ export namespace GooglePhotosUploadUtils {
};
export const DispatchGooglePhotosUpload = async (url: string) => {
+ if (!DashUploadUtils.imageFormats.includes(path.extname(url))) {
+ return undefined;
+ }
const body = await request(url, { encoding: null });
const parameters = {
method: 'POST',
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 24bad694d..0fbfbf2f3 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -46,15 +46,19 @@ export class CurrentUserUtils {
// setup workspaces library item
if (doc.workspaces === undefined) {
- const workspaces = Docs.Create.TreeDocument([], { title: "Workspaces", height: 100 });
+ const workspaces = Docs.Create.TreeDocument([], { title: "Workspaces".toUpperCase(), height: 100 });
workspaces.boxShadow = "0 0";
doc.workspaces = workspaces;
}
PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => {
if (workspaces) {
+ workspaces.backgroundColor = "#eeeeee";
workspaces.preventTreeViewOpen = true;
workspaces.forceActive = true;
workspaces.lockedPosition = true;
+ if (StrCast(workspaces.title) === "Workspaces") {
+ workspaces.title = "WORKSPACES";
+ }
}
});
@@ -71,15 +75,19 @@ export class CurrentUserUtils {
// setup Recently Closed library item
if (doc.recentlyClosed === undefined) {
- const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed", height: 75 });
+ const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed".toUpperCase(), height: 75 });
recentlyClosed.boxShadow = "0 0";
doc.recentlyClosed = recentlyClosed;
}
PromiseValue(Cast(doc.recentlyClosed, Doc)).then(recent => {
if (recent) {
+ recent.backgroundColor = "#eeeeee";
recent.preventTreeViewOpen = true;
recent.forceActive = true;
recent.lockedPosition = true;
+ if (StrCast(recent.title) === "Recently Closed") {
+ recent.title = "RECENTLY CLOSED";
+ }
}
});
@@ -97,10 +105,14 @@ export class CurrentUserUtils {
sidebar.gridGap = 5;
sidebar.xMargin = 5;
sidebar.yMargin = 5;
- Doc.GetProto(sidebar).backgroundColor = "#aca3a6";
sidebar.boxShadow = "1 1 3";
doc.sidebar = sidebar;
}
+ PromiseValue(Cast(doc.sidebar, Doc)).then(sidebar => {
+ if (sidebar) {
+ sidebar.backgroundColor = "lightgrey";
+ }
+ });
if (doc.overlays === undefined) {
const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" });
@@ -112,7 +124,9 @@ export class CurrentUserUtils {
PromiseValue(Cast(doc.overlays, Doc)).then(overlays => overlays && Doc.AddDocToList(overlays, "data", doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" })));
}
- StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library");
+ StrCast(doc.title).indexOf("@") !== -1 && (doc.title = (StrCast(doc.title).split("@")[0] + "'s Library").toUpperCase());
+ StrCast(doc.title).indexOf("'s Library") !== -1 && (doc.title = StrCast(doc.title).toUpperCase());
+ doc.backgroundColor = "#eeeeee";
doc.width = 100;
doc.preventTreeViewOpen = true;
doc.forceActive = true;
diff --git a/src/server/index.ts b/src/server/index.ts
index 526cc6cce..30513ef36 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -946,7 +946,22 @@ app.post(RouteStore.googleDocs + "/:sector/:action", (req, res) => {
});
});
-app.get(RouteStore.googlePhotosAccessToken, (req, res) => GoogleApiServerUtils.RetrieveAccessToken({ credentialsPath, userId: req.header("userId")! }).then(token => res.send(token)));
+app.get(RouteStore.readGooglePhotosAccessToken, async (req, res) => {
+ const userId = req.header("userId")!;
+ const token = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId);
+ const information = { credentialsPath, userId };
+ if (!token) {
+ return res.send(await GoogleApiServerUtils.GenerateAuthenticationUrl(information));
+ }
+ GoogleApiServerUtils.RetrieveAccessToken(information).then(token => res.send(token));
+});
+
+app.post(RouteStore.writeGooglePhotosAccessToken, async (req, res) => {
+ const userId = req.header("userId")!;
+ const information = { credentialsPath, userId };
+ const { token } = await GoogleApiServerUtils.ProcessClientSideCode(information, req.body.authenticationCode);
+ res.send(token.access_token);
+});
const tokenError = "Unable to successfully upload bytes for all images!";
const mediaError = "Unable to convert all uploaded bytes to media items!";
@@ -969,16 +984,17 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => {
await GooglePhotosUploadUtils.initialize({ credentialsPath, userId });
- let failed = 0;
+ let failed: number[] = [];
const newMediaItems = await BatchedArray.from<GooglePhotosUploadUtils.MediaInput>(media, { batchSize: 25 }).batchedMapPatientInterval(
{ magnitude: 100, unit: TimeUnit.Milliseconds },
async (batch: GooglePhotosUploadUtils.MediaInput[]) => {
const newMediaItems: NewMediaItem[] = [];
- for (let element of batch) {
+ for (let index = 0; index < batch.length; index++) {
+ const element = batch[index];
const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(element.url);
if (!uploadToken) {
- failed++;
+ failed.push(index);
} else {
newMediaItems.push({
description: element.description,
@@ -990,12 +1006,13 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => {
}
);
- if (failed) {
- return _error(res, tokenError);
+ const failedCount = failed.length;
+ if (failedCount) {
+ console.log(`Unable to upload ${failedCount} image${failedCount === 1 ? "" : "s"} to Google's servers`);
}
GooglePhotosUploadUtils.CreateMediaItems(newMediaItems, req.body.album).then(
- result => _success(res, result.newMediaItemResults),
+ result => _success(res, { results: result.newMediaItemResults, failed }),
error => _error(res, mediaError, error)
);
});