aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts83
-rw-r--r--src/client/documents/DocumentTypes.ts1
-rw-r--r--src/client/documents/Documents.ts23
-rw-r--r--src/client/util/CurrentUserUtils.ts12
-rw-r--r--src/client/util/DocumentManager.ts44
-rw-r--r--src/client/util/DragManager.ts7
-rw-r--r--src/client/util/DropConverter.ts2
-rw-r--r--src/client/util/GroupManager.tsx36
-rw-r--r--src/client/util/InteractionUtils.tsx46
-rw-r--r--src/client/util/SharingManager.tsx51
-rw-r--r--src/client/views/ContextMenuItem.tsx4
-rw-r--r--src/client/views/DocComponent.tsx13
-rw-r--r--src/client/views/DocumentButtonBar.tsx4
-rw-r--r--src/client/views/DocumentDecorations.scss5
-rw-r--r--src/client/views/DocumentDecorations.tsx78
-rw-r--r--src/client/views/GestureOverlay.tsx90
-rw-r--r--src/client/views/GlobalKeyHandler.ts1
-rw-r--r--src/client/views/InkingStroke.tsx97
-rw-r--r--src/client/views/MainView.tsx40
-rw-r--r--src/client/views/RecommendationsBox.scss69
-rw-r--r--src/client/views/RecommendationsBox.tsx201
-rw-r--r--src/client/views/TemplateMenu.tsx9
-rw-r--r--src/client/views/Touchable.tsx2
-rw-r--r--src/client/views/collections/CollectionMenu.tsx329
-rw-r--r--src/client/views/collections/CollectionPileView.tsx28
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx22
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx4
-rw-r--r--src/client/views/collections/CollectionView.tsx36
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx22
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx40
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx19
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.scss4
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx376
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx4
-rw-r--r--src/client/views/nodes/ColorBox.tsx1
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx2
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx52
-rw-r--r--src/client/views/nodes/DocumentView.tsx472
-rw-r--r--src/client/views/nodes/FontIconBox.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx5
-rw-r--r--src/client/views/nodes/LinkCreatedBox.tsx31
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx4
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx2
-rw-r--r--src/client/views/nodes/TaskCompletedBox.scss (renamed from src/client/views/nodes/LinkCreatedBox.scss)3
-rw-r--r--src/client/views/nodes/TaskCompletedBox.tsx32
-rw-r--r--src/client/views/nodes/WebBox.scss222
-rw-r--r--src/client/views/nodes/WebBox.tsx405
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx22
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts7
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts2
-rw-r--r--src/client/views/pdf/PDFMenu.tsx7
-rw-r--r--src/client/views/pdf/PDFViewer.tsx12
-rw-r--r--src/fields/Doc.ts66
-rw-r--r--src/fields/Schema.ts4
-rw-r--r--src/fields/ScriptField.ts4
-rw-r--r--src/fields/documentSchemas.ts1
-rw-r--r--src/fields/util.ts47
61 files changed, 1770 insertions, 1448 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index bac324c77..dec8724c6 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,13 +1,14 @@
import * as io from 'socket.io-client';
import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
-import { Opt, Doc, fetchProto } from '../fields/Doc';
+import { Opt, Doc, fetchProto, FieldsSym, UpdatingFromServer } from '../fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../fields/RefField';
-import { Id, HandleUpdate } from '../fields/FieldSymbols';
+import { Id, HandleUpdate, Parent } from '../fields/FieldSymbols';
import GestureOverlay from './views/GestureOverlay';
import MobileInkOverlay from '../mobile/MobileInkOverlay';
import { runInAction } from 'mobx';
+import { ObjectField } from '../fields/ObjectField';
/**
* This class encapsulates the transfer and cross-client synchronization of
@@ -207,12 +208,12 @@ export namespace DocServer {
* the server if the document has not been cached.
* @param id the id of the requested document
*/
- const _GetRefFieldImpl = (id: string): Promise<Opt<RefField>> => {
+ const _GetRefFieldImpl = (id: string, force: boolean = false): Promise<Opt<RefField>> => {
// an initial pass through the cache to determine whether the document needs to be fetched,
// is already in the process of being fetched or already exists in the
// cache
const cached = _cache[id];
- if (cached === undefined) {
+ if (cached === undefined || force) {
// NOT CACHED => we'll have to send a request to the server
// synchronously, we emit a single callback to the server requesting the serialized (i.e. represented by a string)
@@ -226,7 +227,19 @@ export namespace DocServer {
const deserializeField = getSerializedField.then(async fieldJson => {
// deserialize
const field = await SerializationHelper.Deserialize(fieldJson);
- if (field !== undefined) {
+ if (force && field instanceof Doc && cached instanceof Doc) {
+ cached[UpdatingFromServer] = true;
+ Array.from(Object.keys(field)).forEach(key => {
+ const fieldval = field[key];
+ if (fieldval instanceof ObjectField) {
+ fieldval[Parent] = undefined;
+ }
+ cached[key] = field[key];
+ });
+ cached[UpdatingFromServer] = false;
+ return cached;
+ }
+ else if (field !== undefined) {
_cache[id] = field;
} else {
delete _cache[id];
@@ -238,8 +251,8 @@ export namespace DocServer {
});
// here, indicate that the document associated with this id is currently
// being retrieved and cached
- _cache[id] = deserializeField;
- return deserializeField;
+ !force && (_cache[id] = deserializeField);
+ return force ? cached as any : deserializeField;
} else if (cached instanceof Promise) {
// BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s),
// and requested the document I'm looking for. Shouldn't fetch again, just
@@ -260,11 +273,11 @@ export namespace DocServer {
}
};
- let _GetRefField: (id: string) => Promise<Opt<RefField>> = errorFunc;
+ let _GetRefField: (id: string, force: boolean) => Promise<Opt<RefField>> = errorFunc;
let _GetCachedRefField: (id: string) => Opt<RefField> = errorFunc;
- export function GetRefField(id: string): Promise<Opt<RefField>> {
- return _GetRefField(id);
+ export function GetRefField(id: string, force = false): Promise<Opt<RefField>> {
+ return _GetRefField(id, force);
}
export function GetCachedRefField(id: string): Opt<RefField> {
return _GetCachedRefField(id);
@@ -330,29 +343,35 @@ export namespace DocServer {
const proms: Promise<void>[] = [];
runInAction(() => {
for (const field of fields) {
- if (field !== undefined && field !== null) {
+ if (field !== undefined && field !== null && !_cache[field.id]) {
// deserialize
- const prom = SerializationHelper.Deserialize(field).then(deserialized => {
- fieldMap[field.id] = deserialized;
-
- //overwrite or delete any promises (that we inserted as flags
- // to indicate that the field was in the process of being fetched). Now everything
- // should be an actual value within or entirely absent from the cache.
- if (deserialized !== undefined) {
- _cache[field.id] = deserialized;
- } else {
- delete _cache[field.id];
- }
- return deserialized;
- });
- // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
- // we set the value at the field's id to a promise that will resolve to the field.
- // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
- // The mapping in the .then call ensures that when other callers await these promises, they'll
- // get the resolved field
- _cache[field.id] = prom;
- // adds to a list of promises that will be awaited asynchronously
- proms.push(prom);
+ const cached = _cache[field.id];
+ if (!cached) {
+ const prom = SerializationHelper.Deserialize(field).then(deserialized => {
+ fieldMap[field.id] = deserialized;
+
+ //overwrite or delete any promises (that we inserted as flags
+ // to indicate that the field was in the process of being fetched). Now everything
+ // should be an actual value within or entirely absent from the cache.
+ if (deserialized !== undefined) {
+ _cache[field.id] = deserialized;
+ } else {
+ delete _cache[field.id];
+ }
+ return deserialized;
+ });
+ // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache)
+ // we set the value at the field's id to a promise that will resolve to the field.
+ // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method).
+ // The mapping in the .then call ensures that when other callers await these promises, they'll
+ // get the resolved field
+ _cache[field.id] = prom;
+
+ // adds to a list of promises that will be awaited asynchronously
+ proms.push(prom);
+ } else if (cached instanceof Promise) {
+ proms.push(cached as any);
+ }
}
}
});
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 7578b7df0..985fcce11 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -36,6 +36,5 @@ export enum DocumentType {
LINKDB = "linkdb", // database of links ??? why do we have this
SCRIPTDB = "scriptdb", // database of scripts
- RECOMMENDATION = "recommendation", // view of a recommendation
GROUPDB = "groupdb" // database of groups
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 529a25bd9..045f7da76 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -45,7 +45,6 @@ import { SliderBox } from "../views/nodes/SliderBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
import { PresElementBox } from "../views/presentationview/PresElementBox";
-import { RecommendationsBox } from "../views/RecommendationsBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
import { DocumentType } from "./DocumentTypes";
import { Networking } from "../Network";
@@ -301,10 +300,6 @@ export namespace Docs {
layout: { view: FontIconBox, dataField: defaultDataKey },
options: { _width: 40, _height: 40, borderRounding: "100%" },
}],
- [DocumentType.RECOMMENDATION, {
- layout: { view: RecommendationsBox, dataField: defaultDataKey },
- options: { _width: 200, _height: 200 },
- }],
[DocumentType.WEBCAM, {
layout: { view: DashWebRTCVideo, dataField: defaultDataKey }
}],
@@ -722,11 +717,11 @@ export namespace Docs {
}
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Freeform }, id);
}
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", ...options, _viewType: CollectionViewType.Pile }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", hideFilterView: true, forceActive: true, ...options, _viewType: CollectionViewType.Pile }, id);
}
export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
@@ -750,11 +745,11 @@ export namespace Docs {
}
export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Tree }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Tree }, id);
}
export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Stacking }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Stacking }, id);
}
export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
@@ -766,7 +761,7 @@ export namespace Docs {
export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Masonry });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Masonry });
}
export function LabelDocument(options?: DocumentOptions) {
@@ -805,10 +800,6 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.IMPORT), new List<Doc>(), options);
}
- export function RecommendationsDocument(data: Doc[], options: DocumentOptions = {}) {
- return InstanceFromProto(Prototypes.get(DocumentType.RECOMMENDATION), new List<Doc>(data), options);
- }
-
export type DocConfig = {
doc: Doc,
initialWidth?: number,
@@ -926,9 +917,6 @@ export namespace DocUtils {
linkDoc.layout_linkView = Cast(Cast(Doc.UserDoc()["template-button-link"], Doc, null).dragFactory, Doc, null);
Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('self.anchor1?.title +" (" + (self.linkRelationship||"to") +") " + self.anchor2?.title');
- Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(self)");
- Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(self)");
-
return linkDoc;
}
@@ -1055,6 +1043,7 @@ export namespace DocUtils {
}
});
batch.end();
+ return doc;
}
export function findTemplate(templateName: string, type: string, signature: string) {
let docLayoutTemplate: Opt<Doc>;
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1fe611b12..24ffa8b1b 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -346,6 +346,14 @@ export class CurrentUserUtils {
iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
}
+ if (doc["template-icon-view-button"] === undefined) {
+ const iconBtnView = Docs.Create.FontIconDocument({
+ title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30,
+ _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+ });
+ iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON);
+ doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView);
+ }
if (doc["template-icon-view-img"] === undefined) {
const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
@@ -359,11 +367,11 @@ export class CurrentUserUtils {
doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
}
if (doc["template-icons"] === undefined) {
- doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+ doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75 }));
} else {
const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
- const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+ const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
await Promise.all(curIcons!);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 51b50878d..523dbfca0 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -152,18 +152,18 @@ export class DocumentManager {
const first = getFirstDocView(annotatedDoc);
if (first) {
annotatedDoc = first.props.Document;
- if (docView) {
- docView.props.focus(annotatedDoc, false);
- }
+ docView?.props.focus(annotatedDoc, false);
}
}
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?
- if (originatingDoc?.isPushpin) docView.props.Document.hidden = !docView.props.Document.hidden;
+ if (originatingDoc?.isPushpin) {
+ docView.props.Document.hidden = !docView.props.Document.hidden;
+ }
else {
docView.props.Document.hidden && (docView.props.Document.hidden = undefined);
docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish);
+ highlight();
}
- highlight();
} else {
const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined;
const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined;
@@ -218,25 +218,27 @@ export class DocumentManager {
const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
- const linkDoc = linkDocList.length && linkDocList[0];
- if (linkDoc) {
- const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
- (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
- const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
- doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
- (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
- if (target) {
- const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
- containerDoc.currentTimecode = targetTimecode;
- const targetContext = await target?.context as Doc;
- const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
- DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+ const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
+ followLinks.forEach(async linkDoc => {
+ if (linkDoc) {
+ const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 :
+ (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc;
+ const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") :
+ doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") :
+ (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number")));
+ if (target) {
+ const containerDoc = (await Cast(target.annotationOn, Doc)) || target;
+ containerDoc.currentTimecode = targetTimecode;
+ const targetContext = await target?.context as Doc;
+ const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished);
+ } else {
+ finished?.();
+ }
} else {
finished?.();
}
- } else {
- finished?.();
- }
+ });
}
}
Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); }); \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 837f0b1db..4b1860b5c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -224,11 +224,10 @@ export namespace DragManager {
}
// drag a button template and drop a new button
- export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+ export function
+ StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
const finishDrag = (e: DragCompleteEvent) => {
- const bd = params.length > Object.keys(vars).length ?
- Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }) :
- Docs.Create.FontIconDocument({ toolTip: title, z: 1, _nativeWidth: 30, _nativeHeight: 30, _width: 30, _height: 30, title, onClick: ScriptField.MakeScript(script) });
+ const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
initialize?.(bd);
Doc.GetProto(bd)["onClick-paramFieldKeys"] = new List<string>(params);
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f9837298d..d0acf14c3 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -57,7 +57,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
data?.draggedDocuments.map((doc, i) => {
let dbox = doc;
// bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
- if (doc.type === DocumentType.FONTICON) {
+ if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes("FontIconBox")) {
dbox = Doc.MakeAlias(doc);
} else if (!doc.onDragStart && !doc.isButtonBar) {
const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 2e5ecc543..72fba5c1b 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -16,6 +16,7 @@ import { StrCast, Cast } from "../../fields/Types";
import GroupMemberView from "./GroupMemberView";
import { setGroups } from "../../fields/util";
import { DocServer } from "../DocServer";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle);
@@ -36,11 +37,13 @@ export default class GroupManager extends React.Component<{}> {
@observable currentGroup: Opt<Doc>; // the currently selected group.
@observable private createGroupModalOpen: boolean = false;
private inputRef: React.RefObject<HTMLInputElement> = React.createRef(); // the ref for the input box.
+ private createGroupButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
private currentUserGroups: string[] = [];
@observable private buttonColour: "#979797" | "black" = "#979797";
@observable private groupSort: "ascending" | "descending" | "none" = "none";
+
constructor(props: Readonly<{}>) {
super(props);
GroupManager.Instance = this;
@@ -48,6 +51,7 @@ export default class GroupManager extends React.Component<{}> {
componentDidMount() {
this.populateUsers();
+ this.populateGroups();
}
/**
@@ -74,6 +78,17 @@ export default class GroupManager extends React.Component<{}> {
return Promise.all(evaluating);
}
+ populateGroups = () => {
+ DocListCastAsync(this.GroupManagerDoc?.data).then(groups => {
+ groups?.forEach(group => {
+ const members: string[] = JSON.parse(StrCast(group.members));
+ if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
+ });
+
+ setGroups(this.currentUserGroups);
+ });
+ }
+
/**
* @returns the options to be rendered in the dropdown menu to add users and create a group.
*/
@@ -89,14 +104,7 @@ export default class GroupManager extends React.Component<{}> {
SelectionManager.DeselectAll();
this.isOpen = true;
this.populateUsers();
- DocListCastAsync(this.GroupManagerDoc?.data).then(groups => {
- groups?.forEach(group => {
- const members: string[] = JSON.parse(StrCast(group.members));
- if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName));
- });
-
- setGroups(this.currentUserGroups);
- });
+ this.populateGroups();
}
/**
@@ -298,6 +306,14 @@ export default class GroupManager extends React.Component<{}> {
this.selectedUsers = null;
this.inputRef.current.value = "";
this.buttonColour = "#979797";
+
+ const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect();
+ TaskCompletionBox.popupX = left - 2 * width;
+ TaskCompletionBox.popupY = top;
+ TaskCompletionBox.textDisplayed = "Group created!";
+ TaskCompletionBox.taskCompleted = true;
+ setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+
}
private get groupCreationModal() {
@@ -340,7 +356,9 @@ export default class GroupManager extends React.Component<{}> {
})
}}
/>
- <button onClick={this.createGroup}
+ <button
+ ref={this.createGroupButtonRef}
+ onClick={this.createGroup}
style={{ background: this.buttonColour }}
disabled={this.buttonColour === "#979797"}
>
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 8b3614ea7..04a750f93 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -99,6 +99,15 @@ export namespace InteractionUtils {
if (shape) { //if any of the shape are true
pts = makePolygon(shape, points);
}
+ else if (points.length >= 5 && points[3].X === points[4].X) {
+ for (var i = 0; i < points.length - 3; i += 4) {
+ const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]];
+ for (var t = 0; t < 1; t += 0.01) {
+ const point = beziercurve(t, array);
+ pts.push({ X: point[0], Y: point[1] });
+ }
+ }
+ }
else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) {
//pointer is up (first and last points are the same)
const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
@@ -118,6 +127,12 @@ export namespace InteractionUtils {
pts.pop();
}
}
+ if (isNaN(scalex)) {
+ scalex = 1;
+ }
+ if (isNaN(scaley)) {
+ scaley = 1;
+ }
const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc +
`${(pt.X - left - width / 2) * scalex + width / 2},
${(pt.Y - top - width / 2) * scaley + width / 2} `, "");
@@ -136,7 +151,6 @@ export namespace InteractionUtils {
<polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} />
</marker>}
</defs>}
-
<polyline
points={strpts}
style={{
@@ -157,17 +171,6 @@ export namespace InteractionUtils {
</svg>);
}
- // export function makeArrow() {
- // return (
- // InkOptionsMenu.Instance.getColors().map(color => {
- // const id1 = "arrowStartTest" + color;
- // <marker id={id1} orient="auto" overflow="visible" refX="0" refY="1" markerWidth="10" markerHeight="7">
- // <polygon points="0 0, 3 1, 0 2" fill={"#" + color} />
- // </marker>;
- // })
- // );
- // }
-
export function makePolygon(shape: string, points: { X: number, Y: number }[]) {
if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
//pointer is up (first and last points are the same)
@@ -217,10 +220,28 @@ export namespace InteractionUtils {
points.push({ X: left, Y: top });
return points;
case "triangle":
+ // points.push({ X: left, Y: bottom });
+ // points.push({ X: right, Y: bottom });
+ // points.push({ X: (right + left) / 2, Y: top });
+ // points.push({ X: left, Y: bottom });
+
+ points.push({ X: left, Y: bottom });
points.push({ X: left, Y: bottom });
+
points.push({ X: right, Y: bottom });
+ points.push({ X: right, Y: bottom });
+ points.push({ X: right, Y: bottom });
+ points.push({ X: right, Y: bottom });
+
+ points.push({ X: (right + left) / 2, Y: top });
+ points.push({ X: (right + left) / 2, Y: top });
points.push({ X: (right + left) / 2, Y: top });
+ points.push({ X: (right + left) / 2, Y: top });
+
+ points.push({ X: left, Y: bottom });
points.push({ X: left, Y: bottom });
+
+
return points;
case "circle":
const centerX = (right + left) / 2;
@@ -256,6 +277,7 @@ export namespace InteractionUtils {
// points.push({ X: x2, Y: y2 });
// return points;
case "line":
+
points.push({ X: left, Y: top });
points.push({ X: right, Y: bottom });
return points;
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 9c857a7c0..0d8b33fbe 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -8,7 +8,6 @@ import * as RequestPromise from "request-promise";
import { Utils } from "../../Utils";
import "./SharingManager.scss";
import { observer } from "mobx-react";
-import { library } from '@fortawesome/fontawesome-svg-core';
import * as fa from '@fortawesome/free-solid-svg-icons';
import { DocumentView } from "../views/nodes/DocumentView";
import { SelectionManager } from "./SelectionManager";
@@ -20,22 +19,14 @@ import GroupMemberView from "./GroupMemberView";
import Select from "react-select";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { List } from "../../fields/List";
-import { distributeAcls } from "../../fields/util";
-
-library.add(fa.faCopy, fa.faTimes);
+import { distributeAcls, SharingPermissions } from "../../fields/util";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
export interface User {
email: string;
userDocumentId: string;
}
-export enum SharingPermissions {
- Edit = "Can Edit",
- Add = "Can Add",
- View = "Can View",
- None = "Not Shared"
-}
-
interface GroupOptions {
label: string;
options: UserOptions[];
@@ -69,6 +60,8 @@ export default class SharingManager extends React.Component<{}> {
@observable private permissions: SharingPermissions = SharingPermissions.Edit;
@observable private individualSort: "ascending" | "descending" | "none" = "none";
@observable private groupSort: "ascending" | "descending" | "none" = "none";
+ private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef();
+
// private get linkVisible() {
@@ -90,6 +83,8 @@ export default class SharingManager extends React.Component<{}> {
public close = action(() => {
this.isOpen = false;
this.users = [];
+ this.selectedUsers = null;
+
setTimeout(action(() => {
// this.copied = false;
DictationOverlay.Instance.hasActiveModal = false;
@@ -235,7 +230,7 @@ export default class SharingManager extends React.Component<{}> {
private get sharingOptions() {
return Object.values(SharingPermissions).map(permission => {
return (
- <option key={permission} value={permission}>
+ <option key={permission} value={permission} selected={permission === SharingPermissions.Edit}>
{permission}
</option>
);
@@ -284,15 +279,25 @@ export default class SharingManager extends React.Component<{}> {
@action
share = () => {
- this.selectedUsers?.forEach(user => {
- if (user.value.includes(indType)) {
- this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
- }
- else {
- this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
- }
- });
- this.selectedUsers = null;
+ if (this.selectedUsers) {
+ this.selectedUsers.forEach(user => {
+ if (user.value.includes(indType)) {
+ this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions);
+ }
+ else {
+ this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+ }
+ });
+
+ const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect();
+ TaskCompletionBox.popupX = left - 1.5 * width;
+ TaskCompletionBox.popupY = top - height;
+ TaskCompletionBox.textDisplayed = "Document shared!";
+ TaskCompletionBox.taskCompleted = true;
+ setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000);
+
+ this.selectedUsers = null;
+ }
}
sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
@@ -439,7 +444,7 @@ export default class SharingManager extends React.Component<{}> {
<div className="sharing-contents">
<p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p>
<div className={"close-button"} onClick={this.close}>
- <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} />
+ <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} />
</div>
{this.targetDoc?.author !== Doc.CurrentUserEmail ? null
:
@@ -456,7 +461,7 @@ export default class SharingManager extends React.Component<{}> {
<select className="permissions-select" onChange={this.handlePermissionsChange}>
{this.sharingOptions}
</select>
- <button className="share-button" onClick={this.share}>
+ <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}>
Share
</button>
</div>
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 86d1f22e1..81432968d 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -20,6 +20,7 @@ export interface SubmenuProps {
description: string;
subitems: ContextMenuProps[];
noexpand?: boolean;
+ addDivider?: boolean;
icon: IconProp; //maybe should be optional (icon?)
closeMenu?: () => void;
}
@@ -103,7 +104,8 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
</div>;
}
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} style={{ alignItems: where }}
+ <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")}
+ style={{ alignItems: where, borderTop: this.props.addDivider ? "solid 1px" : undefined }}
onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
{this.props.icon ? (
<span className="icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: "center" }}>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 95c1bcda8..4c82149e2 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -7,8 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils';
import { List } from '../../fields/List';
import { DateField } from '../../fields/DateField';
import { ScriptField } from '../../fields/ScriptField';
-import { GetEffectiveAcl, getPlaygroundMode } from '../../fields/util';
-import { SharingPermissions } from '../util/SharingManager';
+import { GetEffectiveAcl, getPlaygroundMode, SharingPermissions } from '../../fields/util';
/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
@@ -27,7 +26,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D
// This is the data part of a document -- ie, the data that is constant across all views of the document
@computed get dataDoc() { return this.props.Document[DataSym] as Doc; }
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
}
@@ -60,7 +59,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor:
lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);// && !Doc.SelectedTool(); // bcz: inking state shouldn't affect static tools
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
}
return Component;
}
@@ -115,7 +114,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
return style;
}
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
_annotationKey: string = "annotations";
public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; }
@@ -123,7 +122,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
@action.bound
removeDocument(doc: Doc | Doc[]): boolean {
const docs = doc instanceof Doc ? [doc] : doc;
- docs.map(doc => doc.annotationOn = undefined);
+ docs.map(doc => doc.isPushpin = doc.annotationOn = undefined);
const targetDataDoc = this.dataDoc;
const value = DocListCast(targetDataDoc[this.annotationKey]);
const toRemove = value.filter(v => docs.includes(v));
@@ -151,7 +150,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
if (added.length) {
- if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
+ if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) {
return false;
}
else {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index c99034d81..6b85616c2 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -276,10 +276,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const considerPush = isText && this.considerGoogleDocsPush;
return <div className="documentButtonBar">
<div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+ <DocumentLinksButton links={this.view0.allLinks} View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
</div>
{DocumentLinksButton.StartLink ? <div className="documentButtonBar-button">
- <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+ <DocumentLinksButton links={this.view0.allLinks} View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
</div> : null}
<div className="documentButtonBar-button">
{this.templateButton}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 5948ada88..424a06431 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -157,6 +157,7 @@ $linkGap : 3px;
grid-column-end: 2;
pointer-events: all;
padding-left: 5px;
+ cursor: pointer;
}
.documentDecorations-title {
@@ -204,7 +205,7 @@ $linkGap : 3px;
width: 20px;
}
-.documentDecorations-closeButton {
+.documentDecorations-openInTab {
opacity: 1;
grid-column-start: 4;
grid-column-end: 5;
@@ -216,7 +217,7 @@ $linkGap : 3px;
margin-top: auto;
}
-.documentDecorations-minimizeButton {
+.documentDecorations-closeButton {
opacity: 1;
grid-column-start: 1;
grid-column-end: 3;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index fec4ad9e0..51325ae1b 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -21,7 +21,7 @@ import e = require('express');
import { CollectionDockingView } from './collections/CollectionDockingView';
import { SnappingManager } from '../util/SnappingManager';
import { HtmlField } from '../../fields/HtmlField';
-import { InkData, InkField, InkTool } from "../../fields/InkField";
+import { InkField } from "../../fields/InkField";
import { Tooltip } from '@material-ui/core';
library.add(faCaretUp);
@@ -59,6 +59,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _prevX = 0;
private _prevY = 0;
private _centerPoints: { X: number, Y: number }[] = [];
+ private _inkDocs: { x: number, y: number, width: number, height: number }[] = [];
@observable private _accumulatedTitle = "";
@observable private _titleControlString: string = "#title";
@@ -88,7 +89,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
var [sptX, sptY] = transform.transformPoint(0, 0);
let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
- if (documentView.props.Document.type === DocumentType.LINK) {
+ if (documentView.props.LayoutTemplateString?.includes("LinkAnchorBox")) {
const docuBox = documentView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
if (docuBox.length) {
const rect = docuBox[0].getBoundingClientRect();
@@ -309,8 +310,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- doc._height = (bottom - top) * element.props.ScreenToLocalTransform().Scale;
- doc._width = (right - left) * element.props.ScreenToLocalTransform().Scale;
+ // doc._height = (bottom - top) * element.props.ScreenToLocalTransform().Scale;
+ // doc._width = (right - left) * element.props.ScreenToLocalTransform().Scale;
+ doc._height = (bottom - top);
+ doc._width = (right - left);
}
index++;
@@ -329,6 +332,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
_dragHeights = new Map<Doc, number>();
@action
onPointerDown = (e: React.PointerEvent): void => {
+
+ this._inkDocs = [];
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+ const doc = Document(element.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height) {
+ this._inkDocs.push({ x: doc.x, y: doc.y, width: doc._width, height: doc._height });
+ }
+
+ }));
+
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { });
if (e.button === 0) {
this._resizeHdlId = e.currentTarget.id;
@@ -374,7 +387,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
move[1] = thisPt.thisY - this._snapY;
this._snapX = thisPt.thisX;
this._snapY = thisPt.thisY;
-
+ let dragBottom = false;
let dX = 0, dY = 0, dW = 0, dH = 0;
const unfreeze = () =>
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) =>
@@ -412,6 +425,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
case "documentDecorations-bottomResizer":
unfreeze();
dH = move[1];
+ dragBottom = true;
break;
case "documentDecorations-leftResizer":
unfreeze();
@@ -438,8 +452,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (nwidth / nheight !== width / height) {
height = nheight / nwidth * width;
}
- if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
- else dW = dH * nwidth / nheight;
+ if (!e.ctrlKey && (!dragBottom || !element.layoutDoc._fitWidth)) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
+ if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
+ else dW = dH * nwidth / nheight;
+ }
}
const actualdW = Math.max(width + (dW * scale), 20);
const actualdH = Math.max(height + (dH * scale), 20);
@@ -463,20 +479,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
else if (nwidth > 0 && nheight > 0) {
if (Math.abs(dW) > Math.abs(dH)) {
- if (!fixedAspect) {
+ if (!fixedAspect || e.ctrlKey) {
doc._nativeWidth = actualdW / (doc._width || 1) * (doc._nativeWidth || 0);
}
doc._width = actualdW;
if (fixedAspect && !doc._fitWidth) doc._height = nheight / nwidth * doc._width;
- else doc._height = actualdH;
+ else if (!fixedAspect || !e.ctrlKey) doc._height = actualdH;
}
else {
- if (!fixedAspect) {
+ if (!fixedAspect || e.ctrlKey || (dragBottom && element.layoutDoc._fitWidth)) {
doc._nativeHeight = actualdH / (doc._height || 1) * (doc._nativeHeight || 0);
}
doc._height = actualdH;
if (fixedAspect && !doc._fitWidth) doc._width = nwidth / nheight * doc._height;
- else doc._width = actualdW;
+ else if (!fixedAspect || !e.ctrlKey) doc._width = actualdW;
}
} else {
dW && (doc._width = actualdW);
@@ -502,6 +518,27 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
(e.button === 0) && this._resizeUndo?.end();
this._resizeUndo = undefined;
SnappingManager.clearSnapLines();
+
+
+ //need to change points for resize, or else rotation/control points will fail.
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView, index) => {
+ const doc = Document(element.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var i = 0; i < ink.length; i++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (doc.x - this._inkDocs[index].x) + (ink[i].X * doc._width) / this._inkDocs[index].width;
+ const newY = (doc.y - this._inkDocs[index].y) + (ink[i].Y * doc._height) / this._inkDocs[index].height;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ doc.data = new InkField(newPoints);
+
+ }
+
+ }
+ }));
}
@computed
@@ -549,8 +586,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div className="documentDecorations-contextMenu" onPointerDown={this.onSettingsDown}>
<FontAwesomeIcon size="lg" icon="cog" />
</div></Tooltip>) : (
- <Tooltip title={<><div className="dash-tooltip">Iconify</div></>} placement="top">
- <div className="documentDecorations-minimizeButton" onClick={this.onCloseClick}>
+ <Tooltip title={<><div className="dash-tooltip">Delete</div></>} placement="top">
+ <div className="documentDecorations-closeButton" onClick={this.onCloseClick}>
{/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
<FontAwesomeIcon className="documentdecorations-times" icon={faTimes} size="lg" />
</div></Tooltip>);
@@ -592,6 +629,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (bounds.y > bounds.b) {
bounds.y = bounds.b - (this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight);
}
+ var offset = 0;
+ //make offset larger for ink to edit points
+ if (seldoc.rootDoc.type === DocumentType.INK) {
+ offset = 20;
+ }
return (<div className="documentDecorations" style={{ background: darkScheme }} >
<div className="documentDecorations-background" style={{
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
@@ -604,10 +646,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>
{bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <>
<div className="documentDecorations-container" key="container" ref={this.setTextBar} style={{
- width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
- height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px",
- left: bounds.x - this._resizeBorderWidth / 2,
- top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
+ width: (bounds.r - bounds.x + this._resizeBorderWidth + offset) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + offset) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2 - offset / 2,
+ top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight - offset / 2,
}}>
{maximizeIcon}
{titleArea}
@@ -616,7 +658,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}>
{"_"}
</div></Tooltip>}
- <Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-closeButton" onPointerDown={this.onMaximizeDown}>
+ <Tooltip title={<><div className="dash-tooltip">Open Document In Tab</div></>} placement="top"><div className="documentDecorations-openInTab" onPointerDown={this.onMaximizeDown}>
{SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
</div></Tooltip>
<div id="documentDecorations-rotation" className="documentDecorations-rotation"
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 2e588ceb5..7c0a8635e 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -22,6 +22,7 @@ import { RadialMenu } from "./nodes/RadialMenu";
import HorizontalPalette from "./Palette";
import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
+import * as fitCurve from 'fit-curve';
import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu";
@observer
@@ -63,7 +64,7 @@ export default class GestureOverlay extends Touchable {
private _hands: Map<number, React.Touch[]> = new Map<number, React.Touch[]>();
private _holdTimer: NodeJS.Timeout | undefined;
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
constructor(props: Readonly<{}>) {
super(props);
@@ -630,6 +631,22 @@ export default class GestureOverlay extends Touchable {
// if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
if (!actionPerformed) {
+ const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]);
+ newPoints.pop();
+ const controlPoints: { X: number, Y: number }[] = [];
+
+ const bezierCurves = fitCurve(newPoints, 10);
+ for (const curve of bezierCurves) {
+
+ controlPoints.push({ X: curve[0][0], Y: curve[0][1] });
+ controlPoints.push({ X: curve[1][0], Y: curve[1][1] });
+ controlPoints.push({ X: curve[2][0], Y: curve[2][1] });
+ controlPoints.push({ X: curve[3][0], Y: curve[3][1] });
+
+
+ }
+ this._points = controlPoints;
+
this.dispatchGesture(GestureUtils.Gestures.Stroke);
}
this._points = [];
@@ -649,6 +666,10 @@ export default class GestureOverlay extends Touchable {
}
makePolygon = (shape: string, gesture: boolean) => {
+ //take off gesture recognition for now
+ if (gesture) {
+ return false;
+ }
const xs = this._points.map(p => p.X);
const ys = this._points.map(p => p.Y);
var right = Math.max(...xs);
@@ -684,18 +705,53 @@ export default class GestureOverlay extends Touchable {
//must be (points[0].X,points[0]-1)
case "rectangle":
this._points.push({ X: left, Y: top });
+ this._points.push({ X: left, Y: top });
+
+ this._points.push({ X: right, Y: top });
+ this._points.push({ X: right, Y: top });
this._points.push({ X: right, Y: top });
+ this._points.push({ X: right, Y: top });
+
+ this._points.push({ X: right, Y: bottom });
this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
+
this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: left, Y: bottom });
+
+ this._points.push({ X: left, Y: top });
this._points.push({ X: left, Y: top });
- this._points.push({ X: left, Y: top - 1 });
+ // this._points.push({ X: left, Y: top });
+ // this._points.push({ X: left, Y: top });
+
+ // this._points.push({ X: left, Y: top - 1 });
break;
case "triangle":
+ // this._points.push({ X: left, Y: bottom });
+ // this._points.push({ X: right, Y: bottom });
+ // this._points.push({ X: (right + left) / 2, Y: top });
+ // this._points.push({ X: left, Y: bottom });
+ // this._points.push({ X: left, Y: bottom - 1 });
this._points.push({ X: left, Y: bottom });
+ this._points.push({ X: left, Y: bottom });
+
+ this._points.push({ X: right, Y: bottom });
this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
+ this._points.push({ X: right, Y: bottom });
+
this._points.push({ X: (right + left) / 2, Y: top });
+ this._points.push({ X: (right + left) / 2, Y: top });
+ this._points.push({ X: (right + left) / 2, Y: top });
+ this._points.push({ X: (right + left) / 2, Y: top });
+
+ this._points.push({ X: left, Y: bottom });
this._points.push({ X: left, Y: bottom });
- this._points.push({ X: left, Y: bottom - 1 });
+
+
break;
case "circle":
const centerX = (right + left) / 2;
@@ -712,11 +768,37 @@ export default class GestureOverlay extends Touchable {
}
this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top });
this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top - 1 });
+ // this._points.push({ X: centerX, Y: top });
+ // this._points.push({ X: centerX + radius / 2, Y: top });
+
+ // this._points.push({ X: right, Y: top + radius / 2 });
+ // this._points.push({ X: right, Y: top + radius });
+ // this._points.push({ X: right, Y: top + radius });
+ // this._points.push({ X: right, Y: bottom - radius / 2 });
+
+ // this._points.push({ X: right - radius / 2, Y: bottom });
+ // this._points.push({ X: right - radius, Y: bottom });
+ // this._points.push({ X: right - radius, Y: bottom });
+ // this._points.push({ X: left + radius / 2, Y: bottom });
+
+ // this._points.push({ X: left, Y: bottom - radius / 2 });
+ // this._points.push({ X: left, Y: bottom - radius });
+ // this._points.push({ X: left, Y: bottom - radius });
+ // this._points.push({ X: left, Y: top + radius / 2 });
+
+ // this._points.push({ X: left + radius / 2, Y: top });
+ // this._points.push({ X: left + radius, Y: top });
+
+
+
+
+
+
+
break;
case "line":
this._points.push({ X: left, Y: top });
this._points.push({ X: right, Y: bottom });
- // this._points.push({ X: right, Y: bottom - 1 });
break;
case "arrow":
const x1 = left;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e1232e6f8..b4a518326 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -104,7 +104,6 @@ export default class KeyManager {
}
doDeselect && SelectionManager.DeselectAll();
DictationManager.Controls.stop();
- // RecommendationsBox.Instance.closeMenu();
GoogleAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
GroupManager.Instance.close();
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index e26ad47f9..5892e8346 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -17,6 +17,7 @@ import { Scripting } from "../util/Scripting";
import { Doc } from "../../fields/Doc";
import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
import { action } from "mobx";
+import { setupMoveUpEvents } from "../../Utils";
library.add(faPaintBrush);
@@ -45,6 +46,38 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
FormatShapePane.Instance.Pinned = true;
}
+ private _prevX = 0;
+ private _prevY = 0;
+ private _controlNum = 0;
+ @action
+ onControlDown = (e: React.PointerEvent, i: number): void => {
+ setupMoveUpEvents(this, e, this.onControlMove, this.onControlup, (e) => { });
+ this._prevX = e.clientX;
+ this._prevY = e.clientY;
+ this._controlNum = i;
+ }
+
+ @action
+ changeCurrPoint = (i: number) => {
+ FormatShapePane.Instance._currPoint = i;
+ }
+
+ @action
+ onControlMove = (e: PointerEvent, down: number[]): boolean => {
+ const xDiff = this._prevX - e.clientX;
+ const yDiff = this._prevY - e.clientY;
+ FormatShapePane.Instance.control(xDiff, yDiff, this._controlNum);
+ this._prevX = e.clientX;
+ this._prevY = e.clientY;
+ return false;
+ }
+
+ onControlup = (e: PointerEvent) => {
+ this._prevX = 0;
+ this._prevY = 0;
+ this._controlNum = 0;
+ }
+
public static MaskDim = 50000;
render() {
TraceMobx();
@@ -62,14 +95,74 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const scaleX = (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
const scaleY = (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
const strokeColor = StrCast(this.layoutDoc.color, "");
+
const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker),
StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
+
const hpoints = InteractionUtils.CreatePolyline(data, left, top,
this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15),
StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
"none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true);
+
+ var controlPoints: { X: number, Y: number, I: number }[] = [];
+ var handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = [];
+ var handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = [];
+ if (data.length >= 4) {
+ for (var i = 0; i <= data.length - 4; i += 4) {
+ controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
+ controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 });
+ handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 });
+ handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 });
+ }
+
+ handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 });
+ for (var i = 2; i < data.length - 4; i += 4) {
+
+ handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 });
+
+ }
+ handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 });
+
+
+ }
+ if (data.length <= 4) {
+ handlePoints = [];
+ handleLine = [];
+ controlPoints = [];
+ for (var i = 0; i < data.length; i++) {
+ controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i });
+ }
+
+ }
+ const dotsize = String(Math.min(width * scaleX, height * scaleY) / 40);
+
+ const controls = controlPoints.map((pts, i) =>
+
+ <svg height="10" width="10">
+ <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" stroke-width={String(Number(dotsize) / 2)} fill="red"
+ onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="all-scroll" />
+ </svg>);
+ const handles = handlePoints.map((pts, i) =>
+
+ <svg height="10" width="10">
+ <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" stroke-width={String(Number(dotsize) / 2)} fill="green"
+ onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="all-scroll" display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
+ </svg>);
+ const handleLines = handleLine.map((pts, i) =>
+
+ <svg height="100" width="100">
+ <line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+ x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" stroke-width={String(Number(dotsize) / 2)}
+ display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
+ <line x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2}
+ x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" stroke-width={String(Number(dotsize) / 2)}
+ display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} />
+
+ </svg>);
+
+
return (
<svg className="inkingStroke"
width={width}
@@ -92,6 +185,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
</defs>
{hpoints}
{points}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? controls : ""}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handles : ""}
+ {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handleLines : ""}
+
</svg>
);
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5bf9ac2a3..dd8433866 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,15 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-
-import {
- faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
- faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
- faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
- faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
- faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
- faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
- faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap
-} from '@fortawesome/free-solid-svg-icons';
+import { faHireAHelper, faBuffer } from '@fortawesome/free-brands-svg-icons';
+import * as fa from '@fortawesome/free-solid-svg-icons';
import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -61,7 +52,7 @@ import { DocumentManager } from '../util/DocumentManager';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { LinkMenu } from './linking/LinkMenu';
import { LinkDocPreview } from './nodes/LinkDocPreview';
-import { LinkCreatedBox } from './nodes/LinkCreatedBox';
+import { TaskCompletionBox } from './nodes/TaskCompletedBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
@@ -159,14 +150,21 @@ export class MainView extends React.Component {
}
}
- library.add(faTasks, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
- faTerminal, faToggleOn, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faTimesCircle, faWindowMaximize, faAddressCard, fileSolid,
- faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
- faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
- faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell,
- faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight,
- faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
- faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faMap);
+ library.add(fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt,
+ fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock,
+ fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard,
+ fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight,
+ fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow,
+ fa.faSearch, fa.faFileDownload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft,
+ fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faClipboard,
+ fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt,
+ fa.faFileAudio, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer,
+ fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote,
+ fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes,
+ fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined,
+ fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faChevronLeft, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript,
+ fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper,
+ fa.faBezierCurve, fa.faCircle, fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -639,7 +637,7 @@ export class MainView extends React.Component {
{this.mainContent}
</GestureOverlay>
<PreviewCursor />
- <LinkCreatedBox />
+ <TaskCompletionBox />
<ContextMenu />
<FormatShapePane />
<RadialMenu />
diff --git a/src/client/views/RecommendationsBox.scss b/src/client/views/RecommendationsBox.scss
deleted file mode 100644
index 7d89042a4..000000000
--- a/src/client/views/RecommendationsBox.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-@import "globalCssVariables";
-
-.rec-content *{
- display: inline-block;
- margin: auto;
- width: 50;
- height: 150px;
- border: 1px dashed grey;
- padding: 10px 10px;
-}
-
-.rec-content {
- float: left;
- width: inherit;
- align-content: center;
-}
-
-.rec-scroll {
- overflow-y: scroll;
- overflow-x: hidden;
- position: absolute;
- pointer-events: all;
- // display: flex;
- z-index: 10000;
- box-shadow: gray 0.2vw 0.2vw 0.4vw;
- // flex-direction: column;
- background: whitesmoke;
- padding-bottom: 10px;
- padding-top: 20px;
- // border-radius: 15px;
- border: solid #BBBBBBBB 1px;
- width: 100%;
- text-align: center;
- // max-height: 250px;
- height: 100%;
- text-transform: uppercase;
- color: grey;
- letter-spacing: 2px;
-}
-
-.content {
- padding: 10px;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
-}
-
-.image-background {
- pointer-events: none;
- background-color: transparent;
- width: 50%;
- text-align: center;
- margin-left: 5px;
-}
-
-// bcz: UGH!! Can't have global settings like this!!!
-// img{
-// width: 100%;
-// height: 100%;
-// }
-
-.score {
- // margin-left: 15px;
- width: 50%;
- height: 100%;
- text-align: center;
- margin-left: 10px;
-}
diff --git a/src/client/views/RecommendationsBox.tsx b/src/client/views/RecommendationsBox.tsx
deleted file mode 100644
index 196151e32..000000000
--- a/src/client/views/RecommendationsBox.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { observable, action, computed, runInAction } from "mobx";
-import Measure from "react-measure";
-import "./RecommendationsBox.scss";
-import { Doc, DocListCast, WidthSym, HeightSym } from "../../fields/Doc";
-import { DocumentIcon } from "./nodes/DocumentIcon";
-import { StrCast, NumCast } from "../../fields/Types";
-import { returnFalse, emptyFunction, returnEmptyString, returnOne, emptyPath, returnZero, returnEmptyFilter } from "../../Utils";
-import { Transform } from "../util/Transform";
-import { ObjectField } from "../../fields/ObjectField";
-import { DocumentView } from "./nodes/DocumentView";
-import { DocumentType } from '../documents/DocumentTypes';
-import { ClientRecommender } from "../ClientRecommender";
-import { DocServer } from "../DocServer";
-import { Id } from "../../fields/FieldSymbols";
-import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { DocumentManager } from "../util/DocumentManager";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons";
-import { DocUtils } from "../documents/Documents";
-
-export interface RecProps {
- documents: { preview: Doc, similarity: number }[];
- node: Doc;
-}
-
-library.add(faBullseye, faLink);
-
-@observer
-export class RecommendationsBox extends React.Component<FieldViewProps> {
-
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecommendationsBox, fieldKey); }
-
- // @observable private _display: boolean = false;
- @observable private _pageX: number = 0;
- @observable private _pageY: number = 0;
- @observable private _width: number = 0;
- @observable private _height: number = 0;
- @observable.shallow private _docViews: JSX.Element[] = [];
- // @observable private _documents: { preview: Doc, score: number }[] = [];
- private previewDocs: Doc[] = [];
-
- constructor(props: FieldViewProps) {
- super(props);
- }
-
- @action
- private DocumentIcon(doc: Doc) {
- const layoutresult = StrCast(doc.type);
- let renderDoc = doc;
- //let box: number[] = [];
- if (layoutresult.indexOf(DocumentType.COL) !== -1) {
- renderDoc = Doc.MakeDelegate(renderDoc);
- }
- const returnXDimension = () => 150;
- const returnYDimension = () => 150;
- const scale = () => returnXDimension() / NumCast(renderDoc._nativeWidth, returnXDimension());
- //let scale = () => 1;
- const newRenderDoc = Doc.MakeAlias(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
- newRenderDoc.height = NumCast(this.props.Document.documentIconHeight);
- newRenderDoc.autoHeight = false;
- const docview = <div>
- <DocumentView
- fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1}
- Document={newRenderDoc}
- addDocument={returnFalse}
- LibraryPath={emptyPath}
- removeDocument={returnFalse}
- rootSelected={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- renderDepth={1}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- PanelWidth={returnXDimension}
- PanelHeight={returnYDimension}
- focus={emptyFunction}
- backgroundColor={returnEmptyString}
- parentActive={returnFalse}
- whenActiveChanged={returnFalse}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- ContentScaling={scale}
- />
- </div>;
- return docview;
-
- }
-
- // @action
- // closeMenu = () => {
- // this._display = false;
- // this.previewDocs.forEach(doc => DocServer.DeleteDocument(doc[Id]));
- // this.previewDocs = [];
- // }
-
- // @action
- // resetDocuments = () => {
- // this._documents = [];
- // }
-
- // @action
- // displayRecommendations(x: number, y: number) {
- // this._pageX = x;
- // this._pageY = y;
- // this._display = true;
- // }
-
- static readonly buffer = 20;
-
- // get pageX() {
- // const x = this._pageX;
- // if (x < 0) {
- // return 0;
- // }
- // const width = this._width;
- // if (x + width > window.innerWidth - RecommendationsBox.buffer) {
- // return window.innerWidth - RecommendationsBox.buffer - width;
- // }
- // return x;
- // }
-
- // get pageY() {
- // const y = this._pageY;
- // if (y < 0) {
- // return 0;
- // }
- // const height = this._height;
- // if (y + height > window.innerHeight - RecommendationsBox.buffer) {
- // return window.innerHeight - RecommendationsBox.buffer - height;
- // }
- // return y;
- // }
-
- // get createDocViews() {
- // return DocListCast(this.props.Document.data).map(doc => {
- // return (
- // <div className="content">
- // <span style={{ height: NumCast(this.props.Document.documentIconHeight) }} className="image-background">
- // {this.DocumentIcon(doc)}
- // </span>
- // <span className="score">{NumCast(doc.score).toFixed(4)}</span>
- // <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
- // <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
- // </div>
- // <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "User Selected Link", "Generated from Recommender", undefined)}>
- // <FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" />
- // </div>
- // </div>
- // );
- // });
- // }
-
- componentDidMount() { //TODO: invoking a computedFn from outside an reactive context won't be memoized, unless keepAlive is set
- runInAction(() => {
- if (this._docViews.length === 0) {
- this._docViews = DocListCast(this.props.Document.data).map(doc => {
- return (
- <div className="content">
- <span style={{ height: NumCast(this.props.Document.documentIconHeight) }} className="image-background">
- {this.DocumentIcon(doc)}
- </span>
- <span className="score">{NumCast(doc.score).toFixed(4)}</span>
- <div style={{ marginRight: 50 }} onClick={() => DocumentManager.Instance.jumpToDocument(doc, false)}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={"bullseye"} size="sm" />
- </div>
- <div style={{ marginRight: 50 }} onClick={() => DocUtils.MakeLink({ doc: this.props.Document.sourceDoc as Doc }, { doc: doc }, "Recommender", "", undefined)}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={"link"} size="sm" />
- </div>
- </div>
- );
- });
- }
- });
- }
-
- render() { //TODO: Invariant violation: max depth exceeded error. Occurs when images are rendered.
- // if (!this._display) {
- // return null;
- // }
- // let style = { left: this.pageX, top: this.pageY };
- //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px"
- let title = StrCast((this.props.Document.sourceDoc as Doc).title);
- if (title.length > 15) {
- title = title.substring(0, 15) + "...";
- }
- return (
- <div className="rec-scroll">
- <p>Recommendations for "{title}"</p>
- {this._docViews}
- </div>
- );
- }
- //
- //
-} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 9fb8a227e..eb20fc257 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -63,14 +63,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
this.props.docViews.map(dv => dv.switchViews(false, "layout"));
}
- toggleFloat = (e: React.ChangeEvent<HTMLInputElement>): void => {
- SelectionManager.DeselectAll();
- const topDocView = this.props.docViews[0];
- const ex = e.target.getBoundingClientRect().left;
- const ey = e.target.getBoundingClientRect().top;
- DocumentView.FloatDoc(topDocView, ex, ey);
- }
-
toggleAudio = (e: React.ChangeEvent<HTMLInputElement>): void => {
this.props.docViews.map(dv => dv.props.Document._showAudio = e.target.checked);
}
@@ -127,7 +119,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
this.props.templates.forEach((checked, template) =>
templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
- templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={firstDoc.z ? true : false} toggle={this.toggleFloat} />);
templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template));
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index c4cae7e8d..bb9e108cb 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -12,7 +12,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer;
- protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
protected _touchDrag: boolean = false;
protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 992c1f600..0ca86172f 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
+import { Doc, DocListCast, Opt, Field } from "../../../fields/Doc";
import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
import AntimodeMenu from "../AntimodeMenu";
import "./CollectionMenu.scss";
@@ -24,21 +24,37 @@ import { Document } from "../../../fields/documentSchemas";
import { SelectionManager } from "../../util/SelectionManager";
import { DocumentView } from "../nodes/DocumentView";
import { ColorState } from "react-color";
+import { ObjectField } from "../../../fields/ObjectField";
+import { ScriptField } from "../../../fields/ScriptField";
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { DocUtils } from "../../documents/Documents";
@observer
export default class CollectionMenu extends AntimodeMenu {
static Instance: CollectionMenu;
- @observable SelectedCollection: CollectionView | undefined;
+ @observable SelectedCollection: DocumentView | undefined;
+ @observable FieldKey: string;
constructor(props: Readonly<{}>) {
super(props);
+ this.FieldKey = "";
CollectionMenu.Instance = this;
this._canFade = false; // don't let the inking menu fade away
this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true);
this.jumpTo(300, 300);
}
+ componentDidMount() {
+ reaction(() => SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0],
+ (doc) => doc && this.SetSelection(doc))
+ }
+
+ @action
+ SetSelection(view: DocumentView) {
+ this.SelectedCollection = view;
+ }
+
@action
toggleMenuPin = (e: React.MouseEvent) => {
Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
@@ -53,14 +69,18 @@ export default class CollectionMenu extends AntimodeMenu {
</button>;
return this.getElement(!this.SelectedCollection ? [button] :
- [<CollectionViewBaseChrome key="chrome" CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+ [<CollectionViewBaseChrome key="chrome"
+ docView={this.SelectedCollection}
+ fieldKey={Doc.LayoutFieldKey(this.SelectedCollection?.props.Document)}
+ type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
button]);
}
}
interface CollectionMenuProps {
- CollectionView: CollectionView;
type: CollectionViewType;
+ fieldKey: string;
+ docView: DocumentView;
}
const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@@ -69,61 +89,95 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
export class CollectionViewBaseChrome extends React.Component<CollectionMenuProps> {
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
- get target() { return this.props.CollectionView.props.Document; }
+ get document() { return this.props.docView?.props.Document; }
+ get target() { return this.document; }
_templateCommand = {
params: ["target", "source"], title: "item view",
- script: "this.target.childLayoutTemplate = getDocTemplate(this.source?.[0])",
+ script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])",
immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_narrativeCommand = {
params: ["target", "source"], title: "child click view",
- script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
+ script: "self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])",
immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_contentCommand = {
- params: ["target", "source"], title: "clear content",
- script: "getProto(this.target).data = copyField(this.source);",
+ params: ["target", "source"], title: "set content",
+ script: "getProto(self.target).data = copyField(self.source);",
immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
initialize: emptyFunction,
};
+ _onClickCommand = {
+ params: ["target", "proxy"], title: "copy onClick",
+ script: `{ if (self.proxy?.[0]) {
+ getProto(self.proxy[0]).onClick = copyField(self.target.onClick);
+ getProto(self.proxy[0]).target = self.target.target;
+ getProto(self.proxy[0]).source = copyField(self.target.source);
+ }}`,
+ immediate: undoBatch((source: Doc[]) => { }),
+ initialize: emptyFunction,
+ };
+ _openLinkInCommand = {
+ params: ["target", "container"], title: "link follow target",
+ script: `{ if (self.container?.length) {
+ getProto(self.target).linkContainer = self.container[0];
+ getProto(self.target).isLinkButton = true;
+ getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])");
+ }}`,
+ immediate: undoBatch((container: Doc[]) => {
+ if (container.length) {
+ Doc.GetProto(this.target).linkContainer = container[0];
+ Doc.GetProto(this.target).isLinkButton = true;
+ Doc.GetProto(this.target).onClick = ScriptField.MakeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])");
+ }
+ }),
+ initialize: emptyFunction,
+ };
_viewCommand = {
params: ["target"], title: "bookmark view",
- script: "this.target._panX = this['target-panX']; this.target._panY = this['target-panY']; this.target._viewScale = this['target-viewScale'];",
+ script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale'];",
immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; }),
initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; },
};
_clusterCommand = {
params: ["target"], title: "fit content",
- script: "this.target._fitToBox = !this.target._fitToBox;",
+ script: "self.target._fitToBox = !self.target._fitToBox;",
immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
initialize: emptyFunction
};
_fitContentCommand = {
params: ["target"], title: "toggle clusters",
- script: "this.target.useClusters = !this.target.useClusters;",
+ script: "self.target.useClusters = !self.target.useClusters;",
immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
initialize: emptyFunction
};
+ _saveFilterCommand = {
+ params: ["target"], title: "save filter",
+ script: "self.target._docFilters = copyField(self['target-docFilters']);",
+ immediate: undoBatch((source: Doc[]) => this.target._docFilters = undefined),
+ initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField ? ObjectField.MakeCopy(this.target._docFilters as any as ObjectField) : ""; },
+ };
- _freeform_commands = [this._viewCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+ _freeform_commands = [this._viewCommand, this._saveFilterCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
_stacking_commands = [this._contentCommand, this._templateCommand];
_masonry_commands = [this._contentCommand, this._templateCommand];
_schema_commands = [this._templateCommand, this._narrativeCommand];
+ _doc_commands = [this._openLinkInCommand, this._onClickCommand];
_tree_commands = [];
private get _buttonizableCommands() {
switch (this.props.type) {
+ default: return this._doc_commands;
+ case CollectionViewType.Freeform: return this._freeform_commands;
case CollectionViewType.Tree: return this._tree_commands;
case CollectionViewType.Schema: return this._schema_commands;
case CollectionViewType.Stacking: return this._stacking_commands;
case CollectionViewType.Masonry: return this._stacking_commands;
- case CollectionViewType.Freeform: return this._freeform_commands;
case CollectionViewType.Time: return this._freeform_commands;
case CollectionViewType.Carousel: return this._freeform_commands;
case CollectionViewType.Carousel3D: return this._freeform_commands;
}
- return [];
}
private _picker: any;
private _commandRef = React.createRef<HTMLInputElement>();
@@ -155,24 +209,19 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
this.document._facetWidth = 0;
}
-
@computed get subChrome() {
switch (this.props.type) {
- case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
- default: return null;
+ default:
+ case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />);
+ case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" {...this.props} />);
+ case CollectionViewType.Docking: return (<CollectionDockingChrome key="collchrome" {...this.props} />);
}
}
-
- private get document() {
- return this.props.CollectionView.props.Document;
- }
-
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
this.dropDisposer?.();
@@ -194,15 +243,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
dragViewDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, (e, down, delta) => {
- const vtype = this.props.CollectionView.collectionViewType;
+ const vtype = this.props.type;
const c = {
params: ["target"], title: vtype,
- script: `this.target._viewType = '${StrCast(this.props.CollectionView.props.Document._viewType)}'`,
- immediate: (source: Doc[]) => this.props.CollectionView.props.Document._viewType = Doc.getDocTemplate(source?.[0]),
+ script: `this.target._viewType = '${StrCast(this.props.type)}'`,
+ immediate: (source: Doc[]) => this.document._viewType = Doc.getDocTemplate(source?.[0]),
initialize: emptyFunction,
};
DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title),
- { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY);
+ { target: this.document }, c.params, c.initialize, e.clientX, e.clientY);
return true;
}, emptyFunction, emptyFunction);
}
@@ -210,7 +259,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
setupMoveUpEvents(this, e, (e, down, delta) => {
this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
- { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
+ { target: this.document }, c.params, c.initialize, e.clientX, e.clientY));
return true;
}, emptyFunction, () => {
this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([]));
@@ -244,7 +293,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
className="collectionViewBaseChrome-viewPicker"
onPointerDown={stopPropagation}
onChange={this.viewChanged}
- value={StrCast(this.props.CollectionView.props.Document._viewType)}>
+ value={StrCast(this.props.type)}>
{Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : (
<option
key={Utils.GenerateGuid()}
@@ -264,13 +313,20 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
<div className="collectionMenu-cont" >
<div className="collectionMenu">
<div className="collectionViewBaseChrome">
- {this.viewModes}
- {this.templateChrome}
+ {this.props.type === CollectionViewType.Invalid || this.props.type === CollectionViewType.Docking ? (null) : this.viewModes}
+ {this.props.type === CollectionViewType.Docking ? (null) : this.templateChrome}
<div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
<button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} >
<FontAwesomeIcon icon="filter" size="lg" />
</button>
</div>
+
+ {this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? (null) : <button className={"antimodeMenu-button"} key="float"
+ style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, borderRight: "1px solid gray" }}
+ title="Toggle Overlay Layer"
+ onClick={() => DocumentView.FloatDoc(this.props.docView)}>
+ <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} />
+ </button>}
</div>
{this.subChrome}
</div>
@@ -280,15 +336,22 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
}
@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps> {
+export class CollectionDockingChrome extends React.Component<CollectionMenuProps> {
+ render() {
+ return (null);
+ }
+}
+
+@observer
+export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean }> {
public static Instance: CollectionFreeFormViewChrome;
constructor(props: any) {
super(props);
CollectionFreeFormViewChrome.Instance = this;
}
- get Document() { return this.props.CollectionView.props.Document; }
+ get document() { return this.props.docView.props.Document; }
@computed get dataField() {
- return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
+ return this.document[Doc.LayoutFieldKey(this.document)];
}
@computed get childDocs() {
return DocListCast(this.dataField);
@@ -296,38 +359,44 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
@undoBatch
@action
nextKeyframe = (): void => {
- const currentFrame = Cast(this.Document.currentFrame, "number", null);
+ const currentFrame = Cast(this.document.currentFrame, "number", null);
if (currentFrame === undefined) {
- this.Document.currentFrame = 0;
+ this.document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0);
- this.Document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
- this.Document.lastFrame = Math.max(NumCast(this.Document.currentFrame), NumCast(this.Document.lastFrame));
+ this.document.currentFrame = Math.max(0, (currentFrame || 0) + 1);
+ this.document.lastFrame = Math.max(NumCast(this.document.currentFrame), NumCast(this.document.lastFrame));
}
@undoBatch
@action
prevKeyframe = (): void => {
- const currentFrame = Cast(this.Document.currentFrame, "number", null);
+ const currentFrame = Cast(this.document.currentFrame, "number", null);
if (currentFrame === undefined) {
- this.Document.currentFrame = 0;
+ this.document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
- this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
+ this.document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
}
@undoBatch
@action
miniMap = (): void => {
- this.Document.hideMinimap = !this.Document.hideMinimap;
+ this.document.hideMinimap = !this.document.hideMinimap;
}
private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
private _width = ["1", "5", "10", "100"];
- private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
- private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
- private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
- private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"];
-
+ // private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
+ // private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
+ // private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
+ // private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"];
+ private _dotsize = [10, 20, 30, 40];
+ private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"];
+ private _head = ["", "", "", "arrow", "", ""];
+ private _end = ["", "", "arrow", "arrow", "", ""];
+ private _shape = ["", "line", "line", "line", "rectangle", "circle"];
+ private _title = ["pen", "line", "line with arrow", "line with double arrows", "square", "circle",];
+ private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
@observable _shapesNum = this._shape.length;
@observable _selected = this._shapesNum;
@@ -390,9 +459,11 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
});
return <div className="btn-draw" key="draw">
{this._draw.map((icon, i) =>
- <button className="antimodeMenu-button" key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)}
+ <button className="antimodeMenu-button" title={this._title[i]} key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)}
style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
- {this._draw[i]}
+ {/* {this._draw[i]} */}
+ <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" />
+
</button>)}
</div>;
}
@@ -411,11 +482,11 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
return !this._widthBtn ? widthPicker :
<div className="btn2-group" key="width">
{widthPicker}
- {this._width.map(wid =>
- <button className="antimodeMenu-button" key={wid}
+ {this._width.map((wid, i) =>
+ <button className="antimodeMenu-button" key={wid} title="change width"
onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
- style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
- {wid}
+ style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: "center" }}>
+ •
</button>)}
</div>;
}
@@ -455,33 +526,47 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane"
onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="chevron-right" size="lg" />
+ <FontAwesomeIcon icon="angle-double-right" size="lg" />
</button>;
}
render() {
- return this.Document.isAnnotationOverlay ? (null) :
- <div className="collectionFreeFormMenu-cont">
+ return !this.props.docView.layoutDoc ? (null) : <div className="collectionFreeFormMenu-cont">
+ {this.props.docView.props.renderDepth !== 0 ? (null) :
<div key="map" title="mini map" className="backKeyframe" onClick={this.miniMap}>
<FontAwesomeIcon icon={"map"} size={"lg"} />
</div>
- <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
- <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
- </div>
- <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.Document.editing ? "#759c75" : "#c56565" }}
- onClick={action(() => this.Document.editing = !this.Document.editing)} >
- {NumCast(this.Document.currentFrame)}
- </div>
- <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
- <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
- </div>
+ }
+ <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
+ <FontAwesomeIcon icon={"caret-left"} size={"lg"} />
+ </div>
+ <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }}
+ onClick={action(() => this.document.editing = !this.document.editing)} >
+ {NumCast(this.document.currentFrame)}
+ </div>
+ <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
+ <FontAwesomeIcon icon={"caret-right"} size={"lg"} />
+ </div>
- {this.widthPicker}
- {this.colorPicker}
- {this.fillPicker}
- {this.drawButtons}
- {this.formatPane}
- </div>;
+ {!this.props.isOverlay || this.document.type !== DocumentType.WEB ? (null) :
+ <button className={"antimodeMenu-button"} key="hypothesis"
+ style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, borderRight: "1px solid gray" }}
+ title="Use Hypothesis"
+ onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}>
+ <FontAwesomeIcon icon={["fab", "hire-a-helper"]} size={"lg"} />
+ </button>
+ }
+ {!this.props.isOverlay || this.props.docView.layoutDoc.isAnnotating ?
+ <>
+ {this.drawButtons}
+ {this.widthPicker}
+ {this.colorPicker}
+ {this.fillPicker}
+ {this.formatPane}
+ </> :
+ (null)
+ }
+ </div>;
}
}
@observer
@@ -489,12 +574,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
- @computed private get descending() { return StrCast(this.props.CollectionView.props.Document._columnsSort) === "descending"; }
- @computed get pivotField() { return StrCast(this.props.CollectionView.props.Document._pivotField); }
+ get document() { return this.props.docView.props.Document; }
+
+ @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; }
+ @computed get pivotField() { return StrCast(this.document._pivotField); }
getKeySuggestions = async (value: string): Promise<string[]> => {
value = value.toLowerCase();
- const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+ const docs = DocListCast(this.document[this.props.fieldKey]);
if (docs instanceof Doc) {
return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
} else {
@@ -529,14 +616,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@action
setValue = (value: string) => {
- this.props.CollectionView.props.Document._pivotField = value;
+ this.document._pivotField = value;
return true;
}
@action toggleSort = () => {
- this.props.CollectionView.props.Document._columnsSort =
- this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" :
- this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending";
+ this.document._columnsSort =
+ this.document._columnsSort === "descending" ? "ascending" :
+ this.document._columnsSort === "ascending" ? undefined : "descending";
}
@action resetValue = () => { this._currentKey = this.pivotField; };
@@ -586,35 +673,36 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@observer
export class CollectionSchemaViewChrome extends React.Component<CollectionMenuProps> {
- // private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ get document() { return this.props.docView.props.Document; }
@undoBatch
togglePreview = () => {
const dividerWidth = 4;
const borderWidth = Number(COLLECTION_BORDER_WIDTH);
- const panelWidth = this.props.CollectionView.props.PanelWidth();
- const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+ const panelWidth = this.props.docView.props.PanelWidth();
+ const previewWidth = NumCast(this.document.schemaPreviewWidth);
const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
- this.props.CollectionView.props.Document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
+ this.document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
}
@undoBatch
@action
toggleTextwrap = async () => {
- const textwrappedRows = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []);
if (textwrappedRows.length) {
- this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>([]);
+ this.document.textwrappedSchemaRows = new List<string>([]);
} else {
- const docs = DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey]);
+ const docs = DocListCast(this.document[this.props.fieldKey]);
const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]);
- this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+ this.document.textwrappedSchemaRows = new List<string>(allRows);
}
}
render() {
- const previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
- const textWrapped = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ const previewWidth = NumCast(this.document.schemaPreviewWidth);
+ const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
return (
<div className="collectionSchemaViewChrome-cont">
@@ -634,11 +722,12 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionMenuPr
@observer
export class CollectionTreeViewChrome extends React.Component<CollectionMenuProps> {
+ get document() { return this.props.docView.props.Document; }
get sortAscending() {
- return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
+ return this.document[this.props.fieldKey + "-sortAscending"];
}
set sortAscending(value) {
- this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"] = value;
+ this.document[this.props.fieldKey + "-sortAscending"] = value;
}
@computed private get ascending() {
return Cast(this.sortAscending, "boolean", null);
@@ -669,15 +758,16 @@ export class CollectionTreeViewChrome extends React.Component<CollectionMenuProp
// Enter scroll speed for 3D Carousel
@observer
export class Collection3DCarouselViewChrome extends React.Component<CollectionMenuProps> {
+ get document() { return this.props.docView.props.Document; }
@computed get scrollSpeed() {
- return this.props.CollectionView.props.Document._autoScrollSpeed;
+ return this.document._autoScrollSpeed;
}
@action
setValue = (value: string) => {
const numValue = Number(StrCast(value));
if (numValue > 0) {
- this.props.CollectionView.props.Document._autoScrollSpeed = numValue;
+ this.document._autoScrollSpeed = numValue;
return true;
}
return false;
@@ -714,13 +804,14 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
private decrementLimitReached: boolean = false;
@observable private resize = false;
private resizeListenerDisposer: Opt<Lambda>;
+ get document() { return this.props.docView.props.Document; }
componentDidMount() {
- runInAction(() => this.resize = this.props.CollectionView.props.PanelWidth() < 700);
+ runInAction(() => this.resize = this.props.docView.props.PanelWidth() < 700);
// listener to reduce text on chrome resize (panel resize)
- this.resizeListenerDisposer = computed(() => this.props.CollectionView.props.PanelWidth()).observe(({ newValue }) => {
+ this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => {
runInAction(() => this.resize = newValue < 700);
});
}
@@ -729,7 +820,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
this.resizeListenerDisposer?.();
}
- get numCols() { return NumCast(this.props.CollectionView.props.Document.gridNumCols, 10); }
+ get numCols() { return NumCast(this.document.gridNumCols, 10); }
/**
* Sets the value of `numCols` on the grid's Document to the value entered.
@@ -738,7 +829,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" || e.key === "Tab") {
if (e.currentTarget.valueAsNumber > 0) {
- this.props.CollectionView.props.Document.gridNumCols = e.currentTarget.valueAsNumber;
+ this.document.gridNumCols = e.currentTarget.valueAsNumber;
}
}
@@ -750,8 +841,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
// @undoBatch
// onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
// if (e.key === "Enter" || e.key === "Tab") {
- // if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) {
- // this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber;
+ // if (e.currentTarget.valueAsNumber > 0 && this.document.rowHeight as number !== e.currentTarget.valueAsNumber) {
+ // this.document.rowHeight = e.currentTarget.valueAsNumber;
// }
// }
// }
@@ -761,7 +852,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
*/
@undoBatch
toggleFlex = () => {
- this.props.CollectionView.props.Document.gridFlex = !BoolCast(this.props.CollectionView.props.Document.gridFlex, true);
+ this.document.gridFlex = !BoolCast(this.document.gridFlex, true);
}
/**
@@ -769,8 +860,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
*/
onIncrementButtonClick = () => {
this.clicked = true;
- this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)--;
- undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1)();
+ this.entered && (this.document.gridNumCols as number)--;
+ undoBatch(() => this.document.gridNumCols = this.numCols + 1)();
this.entered = false;
}
@@ -780,8 +871,8 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
onDecrementButtonClick = () => {
this.clicked = true;
if (!this.decrementLimitReached) {
- this.entered && (this.props.CollectionView.props.Document.gridNumCols as number)++;
- undoBatch(() => this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1)();
+ this.entered && (this.document.gridNumCols as number)++;
+ undoBatch(() => this.document.gridNumCols = this.numCols - 1)();
}
this.entered = false;
}
@@ -792,7 +883,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
incrementValue = () => {
this.entered = true;
if (!this.clicked && !this.decrementLimitReached) {
- this.props.CollectionView.props.Document.gridNumCols = this.numCols + 1;
+ this.document.gridNumCols = this.numCols + 1;
}
this.decrementLimitReached = false;
this.clicked = false;
@@ -805,7 +896,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
this.entered = true;
if (!this.clicked) {
if (this.numCols !== 1) {
- this.props.CollectionView.props.Document.gridNumCols = this.numCols - 1;
+ this.document.gridNumCols = this.numCols - 1;
}
else {
this.decrementLimitReached = true;
@@ -819,7 +910,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
* Toggles the value of preventCollision
*/
toggleCollisions = () => {
- this.props.CollectionView.props.Document.gridPreventCollision = !this.props.CollectionView.props.Document.gridPreventCollision;
+ this.document.gridPreventCollision = !this.document.gridPreventCollision;
}
/**
@@ -827,7 +918,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
*/
changeCompactType = (e: React.ChangeEvent<HTMLSelectElement>) => {
// need to change startCompaction so that this operation will be undoable.
- this.props.CollectionView.props.Document.gridStartCompaction = e.target.selectedOptions[0].value;
+ this.document.gridStartCompaction = e.target.selectedOptions[0].value;
}
render() {
@@ -845,10 +936,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
<span className="grid-icon">
<FontAwesomeIcon icon="text-height" size="1x" />
</span>
- <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
+ <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} />
</span> */}
<span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
- <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.props.CollectionView.props.Document.gridPreventCollision} />
+ <input type="checkbox" style={{ marginRight: 5 }} onChange={this.toggleCollisions} checked={!this.document.gridPreventCollision} />
<label className="flexLabel">{this.resize ? "Coll" : "Collisions"}</label>
</span>
@@ -856,7 +947,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
style={{ marginRight: 5 }}
onPointerDown={stopPropagation}
onChange={this.changeCompactType}
- value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
+ value={StrCast(this.document.gridStartCompaction, StrCast(this.document.gridCompaction))}>
{["vertical", "horizontal", "none"].map(type =>
<option className="collectionGridViewChrome-viewOption"
onPointerDown={stopPropagation}
@@ -868,11 +959,11 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp
<span className="grid-control" style={{ width: this.resize ? "12%" : "20%" }}>
<input style={{ marginRight: 5 }} type="checkbox" onChange={this.toggleFlex}
- checked={BoolCast(this.props.CollectionView.props.Document.gridFlex, true)} />
+ checked={BoolCast(this.document.gridFlex, true)} />
<label className="flexLabel">{this.resize ? "Flex" : "Flexible"}</label>
</span>
- <button onClick={() => this.props.CollectionView.props.Document.gridResetLayout = true}>
+ <button onClick={() => this.document.gridResetLayout = true}>
{!this.resize ? "Reset" :
<FontAwesomeIcon icon="redo-alt" size="1x" />}
</button>
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 22a3877ab..2e4055256 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -39,7 +39,15 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
@computed get contents() {
return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" ? undefined : "none" }} >
- <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} />
+ <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine}
+ addDocument={(doc: Doc | Doc[]) => {
+ (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d));
+ return this.props.addDocument(doc);
+ }}
+ moveDocument={(doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
+ (doc instanceof Doc ? [doc] : doc).map((d) => Doc.deiconifyView(d));
+ return this.props.moveDocument(doc, targetCollection, addDoc);
+ }} />
</div>;
}
toggleStarburst = action(() => {
@@ -72,24 +80,13 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
}
});
- @undoBatch
- @action
- onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- if (super.onInternalDrop(e, de)) {
- if (de.complete.docDragData) {
- DocUtils.pileup(this.childDocs);
- }
- }
- return true;
- }
-
_undoBatch: UndoManager.Batch | undefined;
pointerDown = (e: React.PointerEvent) => {
let dist = 0;
SnappingManager.SetIsDragging(true);
// this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header
setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
- if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) {
+ if (this.layoutEngine() === "pass" && this.childDocs.length && e.shiftKey) {
dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
if (dist > 100) {
if (!this._undoBatch) {
@@ -110,11 +107,11 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
if (!this.childDocs.length) {
this.props.ContainingCollectionView?.removeDocument(this.props.Document);
}
- }, emptyFunction, false, this.layoutEngine() === "pass" && this.props.isSelected(true)); // this sets _doubleTap
+ }, emptyFunction, e.shiftKey && this.layoutEngine() === "pass", this.layoutEngine() === "pass" && e.shiftKey); // this sets _doubleTap
}
onClick = (e: React.MouseEvent) => {
- if (e.button === 0 && this._doubleTap) {
+ if (e.button === 0) {//} && this._doubleTap) {
SelectionManager.DeselectAll();
this.toggleStarburst();
e.stopPropagation();
@@ -124,7 +121,6 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
render() {
return <div className={"collectionPileView"} onClick={this.onClick} onPointerDown={this.pointerDown}
- ref={this.createDashEventsTarget}
style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
{this.contents}
</div>;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index dd4c34885..0332b4bf2 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -208,7 +208,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
NativeHeight={returnZero}
NativeWidth={returnZero}
fitToBox={false}
- dontRegisterView={this.props.dontRegisterView}
+ dontRegisterView={BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)}
rootSelected={this.rootSelected}
dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8480a56cc..9f78c15eb 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -55,17 +55,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
class CollectionSubView extends DocComponent<X & SubCollectionViewProps, T>(schemaCtor) {
private dropDisposer?: DragManager.DragDropDisposer;
private gestureDisposer?: GestureUtils.GestureEventDisposer;
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
protected _mainCont?: HTMLDivElement;
protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this.dropDisposer?.();
this.gestureDisposer?.();
- this.multiTouchDisposer?.();
+ this._multiTouchDisposer?.();
if (ele) {
this._mainCont = ele;
this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
- this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
+ this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
}
}
protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
@@ -74,7 +74,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
componentWillUnmount() {
this.gestureDisposer?.();
- this.multiTouchDisposer?.();
+ this._multiTouchDisposer?.();
}
@computed get dataDoc() {
@@ -127,7 +127,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc);
const docFilters = this.docFilters();
- const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
+ const viewSpecScript = ScriptCast(this.props.Document.viewSpecScript);
const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []);
return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, docFilters, docRangeFilters, viewSpecScript);
@@ -195,7 +195,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
const res = addedDocs.length ? this.addDocument(addedDocs) : true;
- added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document) || de.embedKey || !this.props.isAnnotationOverlay ? this.addDocument : returnFalse) : res;
+ if (movedDocs.length) {
+ const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || !this.props.isAnnotationOverlay ||
+ Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document);
+ added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse);
+ } else added = res;
} else {
added = this.addDocument(docDragData.droppedDocuments);
}
@@ -292,7 +296,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const reg = new RegExp(Utils.prepend(""), "g");
const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
- Doc.GetProto(htmlDoc)["data-text"] = text;
+ Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc)["text"] = text;
this.props.addDocument(htmlDoc);
if (srcWeb) {
const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any);
@@ -300,7 +304,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
const x = (rect?.x || 0);
const y = NumCast(srcWeb._scrollTop) + (rect?.y || 0);
- const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
+ const anchor = Docs.Create.FreeformDocument([], { _backgroundColor: "transparent", _width: 75, _height: 40, x, y, annotationOn: srcWeb });
anchor.context = srcWeb;
const key = Doc.LayoutFieldKey(srcWeb);
Doc.AddDocToList(srcWeb, key + "-annotations", anchor);
@@ -430,7 +434,7 @@ import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { DocumentType } from "../../documents/DocumentTypes";
import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
-import { CollectionView } from "./CollectionView";
+import { CollectionView, CollectionViewType } from "./CollectionView";
import { SelectionManager } from "../../util/SelectionManager";
import { OverlayView } from "../OverlayView";
import { setTimeout } from "timers";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 651357e5d..705871a6f 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -100,8 +100,8 @@ class TreeView extends React.Component<TreeViewProps> {
childDocList(field: string) {
const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined;
return ((this.props.dataDoc ? DocListCast(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
- (layout ? Cast(layout[field], listSpec(Doc)) : undefined) || // else if there's a layout doc, display it's fields
- Cast(this.doc[field], listSpec(Doc))) as Doc[]; // otherwise use the document's data field
+ (layout ? DocListCast(layout[field]) : undefined) || // else if there's a layout doc, display it's fields
+ DocListCast(this.doc[field])) as Doc[]; // otherwise use the document's data field
}
@computed get childDocs() { return this.childDocList(this.fieldKey); }
@computed get childLinks() { return this.childDocList("links"); }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index e2f78d6f9..42d320308 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
import Lightbox from 'react-image-lightbox-with-rotate';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate, AclAdmin } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
@@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls, SharingPermissions } from '../../../fields/util';
import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -27,12 +27,10 @@ import { InteractionUtils } from '../../util/InteractionUtils';
import { UndoManager } from '../../util/UndoManager';
import { ContextMenu } from "../ContextMenu";
import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import { ScriptBox } from '../ScriptBox';
import { Touchable } from '../Touchable';
import { CollectionCarousel3DView } from './CollectionCarousel3DView';
import { CollectionCarouselView } from './CollectionCarouselView';
import { CollectionDockingView } from "./CollectionDockingView";
-import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionGridView } from './collectionGrid/CollectionGridView';
import { CollectionLinearView } from './CollectionLinearView';
@@ -47,8 +45,7 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
-import CollectionMenu from './CollectionMenu';
-import { SharingPermissions } from '../../util/SharingManager';
+import { ContextMenuProps } from '../ContextMenuItem';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -105,13 +102,14 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@observable private static _safeMode = false;
public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
private AclMap = new Map<symbol, string>([
[AclPrivate, SharingPermissions.None],
[AclReadonly, SharingPermissions.View],
[AclAddonly, SharingPermissions.Add],
- [AclEdit, SharingPermissions.Edit]
+ [AclEdit, SharingPermissions.Edit],
+ [AclAdmin, SharingPermissions.Admin]
]);
get collectionViewType(): CollectionViewType | undefined {
@@ -144,7 +142,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const effectiveAcl = GetEffectiveAcl(this.props.Document);
if (added.length) {
- if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
+ if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) {
return false;
}
else {
@@ -191,7 +189,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@action.bound
removeDocument = (doc: any): boolean => {
- if (GetEffectiveAcl(this.props.Document) === AclEdit || getPlaygroundMode()) {
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || getPlaygroundMode()) {
const docs = doc instanceof Doc ? [doc] : doc as Doc[];
const targetDataDoc = this.props.Document[DataSym];
const value = DocListCast(targetDataDoc[this.props.fieldKey]);
@@ -268,9 +267,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) {
- const existingVm = ContextMenu.Instance.findByDescription(category);
- const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
+ const subItems: ContextMenuProps[] = [];
subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" });
if (addExtras && CollectionView._safeMode) {
ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" });
@@ -288,17 +286,18 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" });
subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
- if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) {
- subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
- }
addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
- !existingVm && ContextMenu.Instance.addItem({ description: category, noexpand: true, subitems: subItems, icon: "eye" });
+
+ const existingVm = ContextMenu.Instance.findByDescription(category);
+ const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
+ catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" });
+ !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: "eye" });
}
onContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- this.setupViewTypes("Add a Perspective...", vtype => {
+ this.setupViewTypes("UI Controls...", vtype => {
const newRendition = Doc.MakeAlias(this.props.Document);
newRendition._viewType = vtype;
this.props.addDocTab(newRendition, "onRight");
@@ -572,7 +571,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ChildLayoutTemplate: this.childLayoutTemplate,
ChildLayoutString: this.childLayoutString,
};
- setTimeout(action(() => this.props.isSelected(true) && (CollectionMenu.Instance.SelectedCollection = this)), 0);
const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
`${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
@@ -587,7 +585,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href
:
""))}
- {(!this.props.isSelected() || this.props.Document.hideFilterView) && !this.props.Document.forceActive ? (null) :
+ {(!this.props.isSelected() && !this.props.Document.forceActive) || this.props.Document.hideFilterView ? (null) :
<div className="collectionView-filterDragger" title="library View Dragger" onPointerDown={this.onPointerDown}
style={{ right: this.facetWidth() - 1, top: this.props.Document._viewType === CollectionViewType.Docking ? "25%" : "55%" }} />
}
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 649406e6c..8c0b8de9d 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -40,15 +40,21 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
this._reaction?.();
}
async fetchDocuments() {
- const aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document)).filter(doc => doc !== this.props.Document);
- const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.Document[Id]}"` });
- const map: Map<Doc, Doc> = new Map;
- const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
- allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
- docs.forEach(doc => map.delete(doc));
+ const aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document));
+ const containerProtoSets = await Promise.all(aliases.map(async alias =>
+ await Promise.all((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
+ const containerProtos = containerProtoSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
+ const containerSets = await Promise.all(Array.from(containerProtos.keys()).map(async container => {
+ return (await SearchUtil.GetAliasesOfDocument(container));
+ }));
+ const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
+ const doclayoutSets = await Promise.all(Array.from(containers.keys()).map(async (dp) => {
+ return (await SearchUtil.GetAliasesOfDocument(dp));
+ }));
+ const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()).keys());
runInAction(() => {
- this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
- this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+ this._docs = doclayouts.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
+ this._otherDocs = [];
});
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index a4fd5384f..b00074cc6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -142,9 +142,16 @@ export function computePivotLayout(
const fieldKey = "data";
const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
+ let nonNumbers = 0;
const pivotFieldKey = toLabel(pivotDoc._pivotField);
childPairs.map(pair => {
- const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+ const lval = pivotFieldKey === "#" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
+ Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+
+ const num = toNumber(pair.layout[pivotFieldKey]);
+ if (num === undefined || Number.isNaN(num)) {
+ nonNumbers++;
+ }
const val = Field.toString(pair.layout[pivotFieldKey] as Field);
if (lval) {
lval.forEach((val, i) => {
@@ -168,13 +175,6 @@ export function computePivotLayout(
});
}
});
- let nonNumbers = 0;
- childPairs.map(pair => {
- const num = toNumber(pair.layout[pivotFieldKey]);
- if (num === undefined || Number.isNaN(num)) {
- nonNumbers++;
- }
- });
const pivotNumbers = nonNumbers / childPairs.length < .1;
if (pivotColumnGroups.size > 10) {
const arrayofKeys = Array.from(pivotColumnGroups.keys());
@@ -434,27 +434,3 @@ function normalizeResults(
payload: gname.payload
})));
}
-
-export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
- return () => {
- const addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
- let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox
- const scriptField = Cast(doc[key], ScriptField);
- const scriptingBox = <ScriptBox initialText={scriptField && scriptField.script.originalScript}
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- onCancel={() => overlayDisposer()} // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below
- onSave={(text, onError) => {
- const script = CompileScript(text, { params, requiredType, typecheck: false });
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join("\n"));
- } else {
- doc[key] = new ScriptField(script);
- overlayDisposer();
- }
- }} />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
- };
- addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
- addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
- };
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 979b21321..57336131a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -57,7 +57,6 @@ export const panZoomSchema = createSchema({
currentTimecode: "number",
displayTimecode: "number",
currentFrame: "number",
- arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
fitToBox: "boolean",
@@ -892,7 +891,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.focus(doc);
} else {
const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
- const offset = annotOn && (contextHgt / 2 * 96 / 72);
+ const offset = annotOn && (contextHgt / 2);
this.props.Document._scrollY = NumCast(doc.y) - offset;
}
@@ -944,7 +943,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
- ...this.props,
+ addDocument: this.props.addDocument,
+ removeDocument: this.props.removeDocument,
+ moveDocument: this.props.moveDocument,
+ pinToPres: this.props.pinToPres,
+ whenActiveChanged: this.props.whenActiveChanged,
NativeHeight: returnZero,
NativeWidth: returnZero,
fitToBox: false,
@@ -1001,10 +1004,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return this.props.addDocTab(doc, where);
});
getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
- const result = this.Document.arrangeScript?.script.run(params, console.log);
- if (result?.success) {
- return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
- }
const layoutDoc = Doc.Layout(params.pair.layout);
const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
@@ -1145,7 +1144,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
componentDidMount() {
super.componentDidMount?.();
- this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
+ this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation },
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
@@ -1243,11 +1242,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
!appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
- const viewctrls = ContextMenu.Instance.findByDescription("View Controls...");
+ const viewctrls = ContextMenu.Instance.findByDescription("UI Controls...");
const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
viewCtrlItems.push({ description: (this.Document.useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
- !viewctrls && ContextMenu.Instance.addItem({ description: "View Controls...", subitems: viewCtrlItems, icon: "eye" });
+ !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems = options && "subitems" in options ? options.subitems : [];
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
index 88876471c..010beb836 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
@@ -24,11 +24,13 @@
.formatShapePane-inputBtn {
width: inherit;
- position: absolute;
+ position: absolute;
}
.sketch-picker {
background: #323232;
+ width: 160px !important;
+ height: 80% !important;
.flexbox-fit {
background: #323232;
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index 4e328d838..ddc282e57 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -12,6 +12,8 @@ import { SelectionManager } from "../../../util/SelectionManager";
import AntimodeMenu from "../../AntimodeMenu";
import "./FormatShapePane.scss";
import { undoBatch } from "../../../util/UndoManager";
+import { ColorState, SketchPicker } from 'react-color';
+import { DocumentView } from "../../../views/nodes/DocumentView"
@observer
export default class FormatShapePane extends AntimodeMenu {
@@ -20,14 +22,16 @@ export default class FormatShapePane extends AntimodeMenu {
private _lastFill = "#D0021B";
private _lastLine = "#D0021B";
private _lastDash = "2";
- private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF"];
private _mode = ["fill-drip", "ruler-combined"];
- @observable private _subOpen = [false, false, false, false];
+ @observable private _subOpen = [false, false];
@observable private _currMode = "fill-drip";
- @observable private _lock = false;
+ @observable _lock = false;
@observable private _fillBtn = false;
@observable private _lineBtn = false;
+ @observable _controlBtn = false;
+ @observable private _controlPoints: { X: number, Y: number }[] = [];
+ @observable _currPoint = -1;
getField(key: string) {
return this.selectedInk?.reduce((p, i) =>
@@ -101,19 +105,58 @@ export default class FormatShapePane extends AntimodeMenu {
upDownButtons = (dirs: string, field: string) => {
switch (field) {
case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
+ // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break;
case "Xps": this.selectedInk?.forEach(i => i.rootDoc.x = NumCast(i.rootDoc.x) + (dirs === "up" ? 10 : -10)); break;
case "Yps": this.selectedInk?.forEach(i => i.rootDoc.y = NumCast(i.rootDoc.y) + (dirs === "up" ? 10 : -10)); break;
case "stk": this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = NumCast(i.rootDoc.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
case "wid": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ //redraw points
const oldWidth = NumCast(i.rootDoc._width);
+ const oldHeight = NumCast(i.rootDoc._height);
+ const oldX = NumCast(i.rootDoc.x);
+ const oldY = NumCast(i.rootDoc.y);
i.rootDoc._width = oldWidth + (dirs === "up" ? 10 : - 10);
this._lock && (i.rootDoc._height = (i.rootDoc._width / oldWidth * NumCast(i.rootDoc._height)));
+ const doc = Document(i.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
+ console.log(doc.x, doc.y, doc._height, doc._width);
+ const ink = Cast(doc.data, InkField)?.inkData;
+ console.log(ink);
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (doc.x - oldX) + (ink[j].X * doc._width) / oldWidth;
+ const newY = (doc.y - oldY) + (ink[j].Y * doc._height) / oldHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ doc.data = new InkField(newPoints);
+ }
+ }
});
break;
case "hgt": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldWidth = NumCast(i.rootDoc._width);
const oldHeight = NumCast(i.rootDoc._height);
- i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
+ const oldX = NumCast(i.rootDoc.x);
+ const oldY = NumCast(i.rootDoc.y); i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
this._lock && (i.rootDoc._width = (i.rootDoc._height / oldHeight * NumCast(i.rootDoc._width)));
+ const doc = Document(i.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
+ console.log(doc.x, doc.y, doc._height, doc._width);
+ const ink = Cast(doc.data, InkField)?.inkData;
+ console.log(ink);
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (doc.x - oldX) + (ink[j].X * doc._width) / oldWidth;
+ const newY = (doc.y - oldY) + (ink[j].Y * doc._height) / oldHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ doc.data = new InkField(newPoints);
+ }
+ }
});
break;
}
@@ -121,12 +164,11 @@ export default class FormatShapePane extends AntimodeMenu {
@undoBatch
@action
- rotate = (degrees: number) => {
- this.selectedInk?.forEach(action(inkView => {
+ rotate = (angle: number) => {
+ const _centerPoints: { X: number, Y: number }[] = [];
+ SelectionManager.SelectedDocuments().forEach(action(inkView => {
const doc = Document(inkView.rootDoc);
if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
- const angle = Number(degrees) - Number(doc.rotation);
- doc.rotation = Number(degrees);
const ink = Cast(doc.data, InkField)?.inkData;
if (ink) {
const xs = ink.map(p => p.X);
@@ -135,143 +177,280 @@ export default class FormatShapePane extends AntimodeMenu {
const top = Math.min(...ys);
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- const _centerPoints: { X: number, Y: number }[] = [];
_centerPoints.push({ X: left, Y: top });
+ }
+ }
+ }));
+
+ var index = 0;
+ SelectionManager.SelectedDocuments().forEach(action(inkView => {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ doc.rotation = Number(doc.rotation) + Number(angle);
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
const newPoints: { X: number, Y: number }[] = [];
for (var i = 0; i < ink.length; i++) {
- const newX = Math.cos(angle) * (ink[i].X - _centerPoints[0].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].X;
- const newY = Math.sin(angle) * (ink[i].X - _centerPoints[0].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].Y;
+ const newX = Math.cos(angle) * (ink[i].X - _centerPoints[index].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].X;
+ const newY = Math.sin(angle) * (ink[i].X - _centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].Y;
newPoints.push({ X: newX, Y: newY });
}
doc.data = new InkField(newPoints);
- const xs2 = newPoints.map(p => p.X);
- const ys2 = newPoints.map(p => p.Y);
- const left2 = Math.min(...xs2);
- const top2 = Math.min(...ys2);
- const right2 = Math.max(...xs2);
- const bottom2 = Math.max(...ys2);
- doc._height = (bottom2 - top2) * inkView.props.ScreenToLocalTransform().Scale;
- doc._width = (right2 - left2) * inkView.props.ScreenToLocalTransform().Scale;
+ const xs = newPoints.map(p => p.X);
+ const ys = newPoints.map(p => p.Y);
+ const left = Math.min(...xs);
+ const top = Math.min(...ys);
+ const right = Math.max(...xs);
+ const bottom = Math.max(...ys);
+
+ doc._height = (bottom - top);
+ doc._width = (right - left);
}
+ index++;
}
}));
}
+ @undoBatch
+ @action
+ control = (xDiff: number, yDiff: number, controlNum: number) => {
+ this.selectedInk?.forEach(action(inkView => {
+ if (this.selectedInk?.length === 1) {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+
+ const newPoints: { X: number, Y: number }[] = [];
+ const order = controlNum % 4;
+ for (var i = 0; i < ink.length; i++) {
+ if (controlNum === i ||
+ (order === 0 && i === controlNum + 1) ||
+ (order === 0 && controlNum !== 0 && i === controlNum - 2) ||
+ (order === 0 && controlNum !== 0 && i === controlNum - 1) ||
+ (order === 3 && i === controlNum - 1) ||
+ (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) ||
+ (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2)
+ || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1))
+ ) {
+ newPoints.push({ X: ink[i].X - (xDiff * inkView.props.ScreenToLocalTransform().Scale), Y: ink[i].Y - (yDiff * inkView.props.ScreenToLocalTransform().Scale) });
+ }
+ else {
+ newPoints.push({ X: ink[i].X, Y: ink[i].Y });
+ }
+ }
+ const oldx = doc.x;
+ const oldy = doc.y;
+ const xs = ink.map(p => p.X);
+ const ys = ink.map(p => p.Y);
+ const left = Math.min(...xs);
+ const top = Math.min(...ys);
+ doc.data = new InkField(newPoints);
+ const xs2 = newPoints.map(p => p.X);
+ const ys2 = newPoints.map(p => p.Y);
+ const left2 = Math.min(...xs2);
+ const top2 = Math.min(...ys2);
+ const right2 = Math.max(...xs2);
+ const bottom2 = Math.max(...ys2);
+ doc._height = (bottom2 - top2);
+ doc._width = (right2 - left2);
+ //if points move out of bounds
- colorPicker(setter: (color: string) => {}) {
- return <div className="btn-group-palette" key="colorpicker" >
- {this._palette.map(color =>
- <button className="antimodeMenu-button" key={color} onPointerDown={undoBatch(action(() => setter(color)))} style={{ zIndex: 1001, position: "relative" }}>
- <div className="color-previewII" style={{ backgroundColor: color }} />
- </button>)}
+ doc.x = oldx - (left - left2);
+ doc.y = oldy - (top - top2);
+
+ }
+ }
+ }
+ }));
+ }
+
+ @undoBatch
+ @action
+ switchStk = (color: ColorState) => {
+ const val = String(color.hex);
+ this.colorStk = val;
+ return true;
+ }
+
+ @undoBatch
+ @action
+ switchFil = (color: ColorState) => {
+ const val = String(color.hex);
+ this.colorFil = val;
+ return true;
+ }
+
+
+ colorPicker(setter: (color: string) => {}, type: string) {
+ return <div className="btn-group-palette" key="colorpicker" style={{ width: 160, margin: 10 }}>
+ <SketchPicker onChange={type === "stk" ? this.switchStk : this.switchFil} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+ color={type === "stk" ? this.colorStk : this.colorFil} />
</div>;
}
inputBox = (key: string, value: any, setter: (val: string) => {}) => {
return <>
- <input style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+ <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
type="text" value={value}
- onChange={e => setter(e.target.value)}
+ onChange={undoBatch(action((e) => setter(e.target.value)))}
autoFocus />
- <button className="antiMenu-Buttonup" key="up" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
+ <button className="antiMenu-Buttonup" key="up1" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
˄
</button>
<br />
- <button className="antiMenu-Buttonup" key="down" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}>
+ <button className="antiMenu-Buttonup" key="down1" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}>
˅
</button>
</>;
}
+ inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
+ return <>
+ {title1}
+ <p style={{ marginTop: -20, right: 70, position: "absolute" }}>{title2}</p>
+
+ <input style={{ color: "black", width: 40, position: "absolute", right: 130 }}
+ type="text" value={value}
+ onChange={e => setter(e.target.value)}
+ autoFocus />
+ <button className="antiMenu-Buttonup" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} style={{ right: 110 }}>
+ ˄
+ </button>
+ <button className="antiMenu-Buttonup" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: 12, right: 110 }}>
+ ˅
+ </button>
+ {title2 === "" ? "" : <>
+ <input style={{ color: "black", width: 40, position: "absolute", right: 20 }}
+ type="text" value={value2}
+ onChange={e => setter2(e.target.value)}
+ autoFocus />
+ <button className="antiMenu-Buttonup" key="up3" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key2)))}>
+ ˄
+ </button>
+ <br />
+ <button className="antiMenu-Buttonup" key="down3" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key2)))} style={{ marginTop: -8 }}>
+ ˅
+ </button></>}
+ </>;
+ }
+
+
colorButton(value: string, setter: () => {}) {
return <>
- <button className="antimodeMenu-button" key="fill" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "absolute", right: 80 }}>
- <FontAwesomeIcon icon="fill-drip" size="lg" />
- <div className="color-previewI" style={{ backgroundColor: value ?? "121212" }} />
+ <button className="antimodeMenu-button" key="color" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "relative", marginTop: -5 }}>
+ <div className="color-previewII" style={{ backgroundColor: value ?? "121212" }} />
+ {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -23, position: "fixed" }}>☒</p> : ""}
+ </button>
+ </>;
+ }
+
+ controlPointsButton() {
+ return <>
+ <button className="antimodeMenu-button" title="Edit points" key="bezier" onPointerDown={action(() => this._controlBtn = this._controlBtn ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._controlBtn ? "black" : "" }}>
+ <FontAwesomeIcon icon="bezier-curve" size="lg" />
+ </button>
+ <button className="antimodeMenu-button" title="Lock ratio" key="ratio" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._lock ? "black" : "" }}>
+ <FontAwesomeIcon icon="lock" size="lg" />
+
+ </button>
+ <button className="antimodeMenu-button" key="rotate" title="Rotate 90˚" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "relative", marginTop: 10, fontSize: 15 }}>
+ ⟲
</button>
<br /> <br />
</>;
}
- @computed get fillButton() { return this.colorButton(this.colorFil, () => this._fillBtn = !this._fillBtn); }
- @computed get lineButton() { return this.colorButton(this.colorStk, () => this._lineBtn = !this._lineBtn); }
+ lockRatioButton() {
+ return <>
+ <button className="antimodeMenu-button" key="lock" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "absolute", right: 80, backgroundColor: this._lock ? "black" : "" }}>
+ {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */}
+ <FontAwesomeIcon icon="lock" size="lg" />
+
+ </button>
+ <br /> <br />
+ </>;
+ }
- @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color); }
- @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color); }
+ rotate90Button() {
+ return <>
+ <button className="antimodeMenu-button" key="rot" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "absolute", right: 80, }}>
+ {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */}
+ ⟲
+
+ </button>
+ <br /> <br />
+ </>;
+ }
+ @computed get fillButton() { return this.colorButton(this.colorFil, () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
+ @computed get lineButton() { return this.colorButton(this.colorStk, () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); }
+
+ @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); }
+ @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); }
@computed get stkInput() { return this.inputBox("stk", this.widthStk, (val: string) => this.widthStk = val); }
- @computed get hgtInput() { return this.inputBox("hgt", this.shapeHgt, (val: string) => this.shapeHgt = val); }
+ @computed get dashInput() { return this.inputBox("dsh", this.widthStk, (val: string) => this.widthStk = val); }
+
+ @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => this.shapeHgt = val, "H:", "wid", this.shapeWid, (val: string) => this.shapeWid = val, "W:"); }
@computed get widInput() { return this.inputBox("wid", this.shapeWid, (val: string) => this.shapeWid = val); }
- @computed get rotInput() { return this.inputBox("rot", this.shapeRot, (val: string) => this.shapeRot = val); }
- @computed get XpsInput() { return this.inputBox("Xps", this.shapeXps, (val: string) => this.shapeXps = val); }
+ @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; return true; }, "∠:", "rot", this.shapeRot, (val: string) => this.shapeRot = val, ""); }
+
+ @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => this.shapeXps = val, "X:", "Yps", this.shapeYps, (val: string) => this.shapeYps = val, "Y:"); }
@computed get YpsInput() { return this.inputBox("Yps", this.shapeYps, (val: string) => this.shapeYps = val); }
- @computed get propertyGroupItems() {
- const fillCheck = <div key="fill" style={{ display: this._subOpen[0] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- <input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={undoBatch(action(() => this.unFilled = true))} />
- No Fill
- <br />
- <input className="formatShapePane-inputBtn" type="radio" checked={this.solidFil} onChange={undoBatch(action(() => this.solidFil = true))} />
- Solid Fill
- <br /> <br />
- {this.solidFil ? "Color" : ""}
- {this.solidFil ? this.fillButton : ""}
- {this._fillBtn && this.solidFil ? this.fillPicker : ""}
- </div>;
+ @computed get controlPoints() { return this.controlPointsButton(); }
+ @computed get lockRatio() { return this.lockRatioButton(); }
+ @computed get rotate90() { return this.rotate90Button(); }
- const markers = <>
- <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
- Arrow Head
- <br />
- <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
- Arrow End
- <br />
- </>;
- const lineCheck = <div key="lineCheck" style={{ display: this._subOpen[1] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- <input className="formatShapePane-inputBtn" type="radio" checked={this.unStrokd} onChange={undoBatch(action(() => this.unStrokd = true))} />
- No Line
- <br />
- <input className="formatShapePane-inputBtn" type="radio" checked={this.solidStk} onChange={undoBatch(action(() => this.solidStk = true))} />
- Solid Line
- <br />
- <input className="formatShapePane-inputBtn" type="radio" checked={this.dashdStk ? true : false} onChange={undoBatch(action(() => this.dashdStk = "2"))} />
- Dash Line
- <br />
- <br />
- {(this.solidStk || this.dashdStk) ? "Color" : ""}
- {(this.solidStk || this.dashdStk) ? this.lineButton : ""}
- {(this.solidStk || this.dashdStk) && this._lineBtn ? this.linePicker : ""}
- <br />
+ @computed get propertyGroupItems() {
+ const fillCheck = <div key="fill" style={{ display: (this._subOpen[0] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
+ Fill:
+ {this.fillButton}
+ <div style={{ float: "left", width: 100 }} >
+ Stroke:
+ {this.lineButton}
+ </div>
+
+ {this._fillBtn ? this.fillPicker : ""}
+ {this._lineBtn ? this.linePicker : ""}
+ {this._fillBtn || this._lineBtn ? "" : <br />}
{(this.solidStk || this.dashdStk) ? "Width" : ""}
{(this.solidStk || this.dashdStk) ? this.stkInput : ""}
- {(this.solidStk || this.dashdStk) ? <input type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={e => this.widthStk = e.target.value} /> : (null)}
- <br /> <br />
- {(this.solidStk || this.dashdStk) ? markers : ""}
- </div>;
- const sizeCheck = <div key="sizeCheck" style={{ display: this._subOpen[2] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- Height {this.hgtInput}
- <br /> <br />
- Width {this.widInput}
- <br /> <br />
- <input className="formatShapePane-inputBtn" style={{ right: 0 }} type="checkbox" checked={this._lock} onChange={undoBatch(action(() => this._lock = !this._lock))} />
- Lock Ratio
- <br /> <br />
- Rotation {this.rotInput}
- <br /> <br />
- </div>;
- const positionCheck = <div key="posCheck" style={{ display: this._subOpen[3] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
- Horizontal {this.XpsInput}
- <br /> <br />
- Vertical {this.YpsInput}
- <br /> <br />
+ {(this.solidStk || this.dashdStk) ? <input type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={undoBatch(action((e) => this.widthStk = e.target.value))} /> : (null)}
+ <br />
+ {(this.solidStk || this.dashdStk) ? <>
+ <p style={{ position: "absolute", fontSize: 12 }}>Arrow Head</p>
+ <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} style={{ position: "absolute", right: 110, width: 20 }} />
+ <p style={{ position: "absolute", fontSize: 12, right: 30 }}>Arrow End</p>
+ <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} style={{ position: "absolute", right: 0, width: 20 }} />
+ <br />
+ </> : ""}
+ Dash: <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.dashdStk === "2"} onChange={undoBatch(action(() => this.dashdStk = this.dashdStk === "2" ? "0" : "2"))} style={{ position: "absolute", right: 110, width: 20 }} />
+
+
+
</div>;
- const subMenus = this._currMode === "fill-drip" ? [`fill`, `line`] : [`size`, `position`];
- const menuItems = this._currMode === "fill-drip" ? [fillCheck, lineCheck] : [sizeCheck, positionCheck];
- const indexOffset = this._currMode === "fill-drip" ? 0 : 2;
+
+
+ const sizeCheck =
+
+ <div key="sizeCheck" style={{ display: (this._subOpen[1] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
+ {this.controlPoints}
+ {this.hgtInput}
+ {this.XpsInput}
+ {this.rotInput}
+
+ </div>;
+
+
+ const subMenus = this._currMode === "fill-drip" ? [`Appearance`, 'Transform'] : [];
+ const menuItems = this._currMode === "fill-drip" ? [fillCheck, sizeCheck] : [];
+ const indexOffset = 0;
+
return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}>
{subMenus.map((subMenu, i) =>
<div key={subMenu} style={{ width: "inherit" }}>
@@ -302,6 +481,7 @@ export default class FormatShapePane extends AntimodeMenu {
}
render() {
- return this.getElementVert([this.closeBtn, this.propertyGroupBtn, this.propertyGroupItems]);
+ return this.getElementVert([this.closeBtn,
+ this.propertyGroupItems]);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index aa9a3b4ae..764758eee 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,6 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly } from "../../../../fields/Doc";
+import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly, AclAdmin } from "../../../../fields/Doc";
import { GetEffectiveAcl, getPlaygroundMode } from "../../../../fields/util";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
@@ -281,7 +281,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downX = x;
this._downY = y;
const effectiveAcl = GetEffectiveAcl(this.props.Document);
- if (effectiveAcl === AclEdit || effectiveAcl === AclAddonly || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+ if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl) || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
this.clearSelection();
}
});
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 57028b0ca..090cf015a 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -61,6 +61,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
<SketchPicker onChange={ColorBox.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
color={StrCast(ActiveInkPen()?.backgroundColor,
StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} />
+
<div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}>
<div> {ActiveInkWidth() ?? 2}</div>
<input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index f140cc6e5..616cddfcf 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -24,7 +24,7 @@ const ComparisonDocument = makeInterface(comparisonSchema, documentSchema);
@observer
export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); }
- protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+ protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
@observable _animating = "";
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 47dc0a773..e8173d103 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -35,7 +35,6 @@ import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
import { InkingStroke } from "../InkingStroke";
import React = require("react");
-import { RecommendationsBox } from "../RecommendationsBox";
import { TraceMobx, GetEffectiveAcl } from "../../../fields/util";
import { ScriptField } from "../../../fields/ScriptField";
import XRegExp = require("xregexp");
@@ -194,7 +193,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
- RecommendationsBox, ScreenshotBox, HTMLtag, ComparisonBox
+ ScreenshotBox, HTMLtag, ComparisonBox
}}
bindings={bindings}
jsx={layoutFrame}
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index e68a85664..445ab6cd4 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -1,25 +1,23 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Tooltip } from "@material-ui/core";
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../fields/Doc";
import { emptyFunction, setupMoveUpEvents, returnFalse, Utils } from "../../../Utils";
+import { TraceMobx } from "../../../fields/util";
+import { DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
-import { UndoManager, undoBatch } from "../../util/UndoManager";
-import './DocumentLinksButton.scss';
+import { LinkManager } from "../../util/LinkManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocumentView } from "./DocumentView";
-import React = require("react");
-import { DocUtils, Docs } from "../../documents/Documents";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { LinkDocPreview } from "./LinkDocPreview";
-import { LinkCreatedBox } from "./LinkCreatedBox";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Document } from "../../../fields/documentSchemas";
import { StrCast } from "../../../fields/Types";
-
import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
-import { LinkManager } from "../../util/LinkManager";
import { Hypothesis } from "../../apis/hypothesis/HypothesisUtils";
import { Id } from "../../../fields/FieldSymbols";
-import { Tooltip } from "@material-ui/core";
+import { TaskCompletionBox } from "./TaskCompletedBox";
+import React = require("react");
+import './DocumentLinksButton.scss';
+
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -30,6 +28,7 @@ interface DocumentLinksButtonProps {
AlwaysOn?: boolean;
InMenu?: boolean;
StartLink?: boolean;
+ links: Doc[];
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -119,15 +118,16 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
runInAction(() => {
if (linkDoc) {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 133;
- LinkCreatedBox.linkCreated = true;
+ TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.popupX = e.screenX;
+ TaskCompletionBox.popupY = e.screenY - 133;
+ TaskCompletionBox.taskCompleted = true;
LinkDescriptionPopup.popupX = e.screenX;
LinkDescriptionPopup.popupY = e.screenY - 100;
LinkDescriptionPopup.descriptionPopup = true;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
}
});
@@ -138,7 +138,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
}
@action @undoBatch
- finishLinkClick = (e: React.MouseEvent) => {
+ finishLinkClick = (screenX: number, screenY: number) => {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.AnnotationId = undefined;
@@ -163,17 +163,18 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
runInAction(() => {
if (linkDoc) {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 133;
- LinkCreatedBox.linkCreated = true;
+ TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.popupX = screenX;
+ TaskCompletionBox.popupY = screenY - 133;
+ TaskCompletionBox.taskCompleted = true;
if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
- LinkDescriptionPopup.popupX = e.screenX;
- LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.popupX = screenX;
+ LinkDescriptionPopup.popupY = screenY - 100;
LinkDescriptionPopup.descriptionPopup = true;
}
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ setTimeout(action(() => { TaskCompletionBox.taskCompleted = false; }), 2500);
}
});
}
@@ -187,7 +188,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@computed
get linkButton() {
- const links = DocListCast(this.props.View.props.Document.links);
+ TraceMobx();
+ const links = this.props.links;
const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
const buttonTitle = "Tap to view links";
@@ -231,7 +233,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
</div>
{DocumentLinksButton.StartLink && this.props.InMenu && !!!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"}
style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }}
- onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e)} /> : (null)}
+ onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e.screenX, e.screenY)} /> : (null)}
{DocumentLinksButton.StartLink === this.props.View && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"}
style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
</div>;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 998c6798e..15cf9556b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,31 +1,31 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import * as fa from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DataSym, AclPrivate, AclEdit } from "../../../fields/Doc";
+import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { Document } from '../../../fields/documentSchemas';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { listSpec } from "../../../fields/Schema";
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
-import { TraceMobx, GetEffectiveAcl } from '../../../fields/util';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
+import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
+import { MobileInterface } from '../../../mobile/MobileInterface';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
-import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
+import { emptyFunction, emptyPath, OmitKeys, returnOne, returnTransparent, Utils } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { ClientRecommender } from '../../ClientRecommender';
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from "../../util/DocumentManager";
-import { SnappingManager } from '../../util/SnappingManager';
import { DragManager, dropActionType } from "../../util/DragManager";
import { InteractionUtils } from '../../util/InteractionUtils';
+import { LinkManager } from '../../util/LinkManager';
import { Scripting } from '../../util/Scripting';
import { SearchUtil } from '../../util/SearchUtil';
import { SelectionManager } from "../../util/SelectionManager";
-import SharingManager, { SharingPermissions } from '../../util/SharingManager';
+import SharingManager from '../../util/SharingManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { CollectionView, CollectionViewType } from '../collections/CollectionView';
@@ -35,19 +35,13 @@ import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
import { KeyphraseQueryView } from '../KeyphraseQueryView';
import { DocumentContentsView } from "./DocumentContentsView";
+import { DocumentLinksButton } from './DocumentLinksButton';
import "./DocumentView.scss";
import { LinkAnchorBox } from './LinkAnchorBox';
+import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { RadialMenu } from './RadialMenu';
+import { TaskCompletionBox } from './TaskCompletedBox';
import React = require("react");
-import { DocumentLinksButton } from './DocumentLinksButton';
-import { MobileInterface } from '../../../mobile/MobileInterface';
-import { LinkCreatedBox } from './LinkCreatedBox';
-import { LinkDescriptionPopup } from './LinkDescriptionPopup';
-import { LinkManager } from '../../util/LinkManager';
-
-library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
- fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
- fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, fa.faQuestion);
export type DocFocusFunc = () => boolean;
@@ -104,24 +98,25 @@ export interface DocumentViewProps {
@observer
export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
+ @observable _animateScalingTo = 0;
private _downX: number = 0;
private _downY: number = 0;
+ private _firstX: number = -1;
+ private _firstY: number = -1;
private _lastTap: number = 0;
private _doubleTap = false;
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
private _showKPQuery: boolean = false;
private _queries: string = "";
- private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
private _titleRef = React.createRef<EditableView>();
+ private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
+ private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- private holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
-
- public get title() { return this.props.Document.title; }
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
- get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
+ private get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@computed get topMost() { return this.props.renderDepth === 0; }
@computed get freezeDimensions() { return this.props.FreezeDimensions; }
@computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@@ -135,9 +130,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClickFunc = () => this.onClickHandler;
onDoubleClickFunc = () => this.onDoubleClickHandler;
- private _firstX: number = -1;
- private _firstY: number = -1;
-
handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
this.removeMoveListeners();
this.removeEndListeners();
@@ -151,11 +143,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._firstX = pt.pageX;
this._firstY = pt.pageY;
}
-
}
handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
-
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
if (this._firstX === -1 || this._firstY === -1) {
@@ -199,7 +189,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
componentDidMount() {
this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document));
this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
- this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
+ this._mainCont.current && (this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)));
// this._mainCont.current && (this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)));
if (!this.props.dontRegisterView) {
@@ -211,13 +201,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
componentDidUpdate() {
this._dropDisposer?.();
this._gestureEventDisposer?.();
- this.multiTouchDisposer?.();
- this.holdDisposer?.();
+ this._multiTouchDisposer?.();
+ this._holdDisposer?.();
if (this._mainCont.current) {
this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this));
- this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
- this.holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+ this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+ this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
}
}
@@ -225,8 +215,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
componentWillUnmount() {
this._dropDisposer?.();
this._gestureEventDisposer?.();
- this.multiTouchDisposer?.();
- this.holdDisposer?.();
+ this._multiTouchDisposer?.();
+ this._holdDisposer?.();
Doc.UnBrushDoc(this.props.Document);
if (!this.props.dontRegisterView) {
const index = DocumentManager.Instance.DocumentViews.indexOf(this);
@@ -241,27 +231,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
dragData.dropAction = dropAction;
dragData.removeDocument = this.props.removeDocument;
- dragData.moveDocument = this.props.moveDocument;// this.layoutDoc.onDragStart ? undefined : this.props.moveDocument;
+ dragData.moveDocument = this.props.moveDocument;
dragData.dragDivName = this.props.dragDivName;
dragData.treeViewDoc = this.props.treeViewDoc;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.layoutDoc.onDragStart });
}
}
- public static FloatDoc(topDocView: DocumentView, x: number, y: number) {
+ @undoBatch @action
+ public static FloatDoc(topDocView: DocumentView, x?: number, y?: number) {
const topDoc = topDocView.props.Document;
- const de = new DragManager.DocumentDragData([topDoc]);
- de.dragDivName = topDocView.props.dragDivName;
- de.moveDocument = topDocView.props.moveDocument;
- undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
- setTimeout(() => {
- const newDocView = DocumentManager.Instance.getDocumentView(topDoc);
- if (newDocView) {
- const contentDiv = newDocView.ContentDiv!;
- const xf = contentDiv.getBoundingClientRect();
- DragManager.StartDocumentDrag([contentDiv], de, x, y, { offsetX: x - xf.left, offsetY: y - xf.top, hideSource: true });
+ const container = topDocView.props.ContainingCollectionView;
+ if (container) {
+ SelectionManager.DeselectAll();
+ if (topDoc.z && (x === undefined && y === undefined)) {
+ const spt = container.screenToLocalTransform().inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
+ topDoc.z = 0;
+ topDoc.x = spt[0];
+ topDoc.y = spt[1];
+ topDocView.props.removeDocument?.(topDoc);
+ topDocView.props.addDocTab(topDoc, "inParent");
+ } else {
+ const spt = topDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const fpt = container.screenToLocalTransform().transformPoint(x !== undefined ? x : spt[0], y !== undefined ? y : spt[1]);
+ topDoc.z = 1;
+ topDoc.x = fpt[0];
+ topDoc.y = fpt[1];
}
- }, 0);
+ setTimeout(() => SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(topDoc, container)!, false), 0);
+ }
}
onKeyDown = (e: React.KeyboardEvent) => {
@@ -294,47 +292,45 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let stopPropagate = true;
let preventDefault = true;
!this.props.Document.isBackground && this.props.bringToFront(this.props.Document);
- if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+ if (this._doubleTap && this.props.renderDepth) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
if (!(e.nativeEvent as any).formattedHandled) {
if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
const func = () => this.onDoubleClickHandler.script.run({
this: this.layoutDoc,
self: this.rootDoc,
- thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+ thisContainer: this.props.ContainingCollectionDoc,
+ shiftKey: e.shiftKey
}, console.log);
func();
} else {
UndoManager.RunInBatch(() => {
+ let fullScreenDoc = this.props.Document;
if (StrCast(this.props.Document.layoutKey) !== "layout_fullScreen" && this.props.Document.layout_fullScreen) {
- const fullScreenAlias = Doc.MakeAlias(this.props.Document);
- fullScreenAlias.layoutKey = "layout_fullScreen";
- this.props.addDocTab(fullScreenAlias, "inTab");
- } else {
- this.props.addDocTab(this.props.Document, "inTab");
+ fullScreenDoc = Doc.MakeAlias(this.props.Document);
+ fullScreenDoc.layoutKey = "layout_fullScreen";
}
+ this.props.addDocTab(fullScreenDoc, "inTab");
}, "double tap");
SelectionManager.DeselectAll();
Doc.UnBrushDoc(this.props.Document);
}
}
} else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
- //SelectionManager.DeselectAll();
const func = () => this.onClickHandler.script.run({
this: this.layoutDoc,
self: this.rootDoc,
- thisContainer: this.props.ContainingCollectionDoc, shiftKey: e.shiftKey
+ thisContainer: this.props.ContainingCollectionDoc,
+ shiftKey: e.shiftKey
}, console.log);
if (this.props.Document !== Doc.UserDoc()["dockedBtn-undo"] && this.props.Document !== Doc.UserDoc()["dockedBtn-redo"]) {
UndoManager.RunInBatch(func, "on click");
} else func();
} else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself
- const alias = Doc.MakeAlias(this.props.Document);
- DocUtils.makeCustomViewClicked(alias, undefined, "onClick");
- this.props.addDocTab(alias, "onRight");
- } else if (this.props.Document.links && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
- DocListCast(this.props.Document.links).length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
+ this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "onRight");
+ } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+ this.allLinks.length && this.followLinkClick(e.altKey, e.ctrlKey, e.shiftKey);
} else {
- if ((this.layoutDoc.onDragStart || (this.props.Document.rootDocument)) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTEmplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+ if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
} else {
SelectionManager.SelectDoc(this, e.ctrlKey || e.shiftKey);
@@ -406,7 +402,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
-
}
}
@@ -444,12 +439,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const oldPoint2 = this.prevPoints.get(pt2.identifier);
const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
if (pinching !== 0 && oldPoint1 && oldPoint2) {
- // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX));
- // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY));
- // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX));
- // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY));
- // let dW = -dX;
- // let dH = -dY;
const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
const dX = -1 * Math.sign(dW);
@@ -532,7 +521,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onPointerMove = (e: PointerEvent): void => {
-
if ((e as any).formattedHandled) { e.stopPropagation(); return; }
if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) return;
if (e.cancelBubble && this.active) {
@@ -553,17 +541,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onPointerUp = (e: PointerEvent): void => {
this.cleanUpInteractions();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
- document.removeEventListener("pointerup", this.onPointerUp);
- return;
+ } else {
+ this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+ this._lastTap = Date.now();
}
-
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
- this._lastTap = Date.now();
}
onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
@@ -585,42 +571,20 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
-
- @undoBatch
- toggleLinkButtonBehavior = (): void => {
- this.Document.ignoreClick = false;
- if (this.Document.isLinkButton || this.onClickHandler || this.Document.ignoreClick) {
- this.Document.isLinkButton = false;
- this.Document.onClick = this.layoutDoc.onClick = undefined;
- } else {
- this.Document.isLinkButton = true;
- this.Document.followLinkZoom = false;
- this.Document.followLinkLocation = undefined;
- }
- }
-
- @undoBatch
- toggleFollowInPlace = (): void => {
+ @undoBatch @action
+ toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
this.Document.ignoreClick = false;
this.Document.isLinkButton = !this.Document.isLinkButton;
- if (this.Document.isLinkButton) {
- this.Document.followLinkZoom = true;
- this.Document.followLinkLocation = "inPlace";
- }
- }
-
- @undoBatch
- toggleFollowOnRight = (): void => {
- this.Document.ignoreClick = false;
- this.Document.isLinkButton = !this.Document.isLinkButton;
- if (this.Document.isLinkButton) {
- this.Document.followLinkZoom = false;
- this.Document.followLinkLocation = "onRight";
+ setPushpin && (this.Document.isPushpin = this.Document.isLinkButton);
+ if (this.Document.isLinkButton && !this.onClickHandler) {
+ this.Document.followLinkZoom = zoom;
+ this.Document.followLinkLocation = location;
+ } else {
+ this.Document.onClick = this.layoutDoc.onClick = undefined;
}
}
- @undoBatch
- @action
+ @undoBatch @action
drop = async (e: Event, de: DragManager.DropEvent) => {
if (this.props.Document === Doc.UserDoc().activeWorkspace) {
alert("linking to document tabs not yet supported. Drop link on document content.");
@@ -629,15 +593,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const makeLink = action((linkDoc: Doc) => {
LinkManager.currentLink = linkDoc;
- LinkCreatedBox.popupX = de.x;
- LinkCreatedBox.popupY = de.y - 33;
- LinkCreatedBox.linkCreated = true;
+ TaskCompletionBox.textDisplayed = "Link Created";
+ TaskCompletionBox.popupX = de.x;
+ TaskCompletionBox.popupY = de.y - 33;
+ TaskCompletionBox.taskCompleted = true;
LinkDescriptionPopup.popupX = de.x;
LinkDescriptionPopup.popupY = de.y;
LinkDescriptionPopup.descriptionPopup = true;
- setTimeout(action(() => LinkCreatedBox.linkCreated = false), 2500);
+ setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500);
});
if (de.complete.annoDragData) {
/// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner
@@ -649,11 +614,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
if (de.complete.linkDragData) {
e.stopPropagation();
- if (de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
- const linkDoc = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
- { doc: this.props.Document }, `link`);
- de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
- (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
+ const linkSource = de.complete.linkDragData.linkSourceDocument;
+ if (linkSource !== this.props.Document) {
+ const linkDoc = DocUtils.MakeLink({ doc: linkSource }, { doc: this.props.Document }, `link`);
+ linkSource !== this.props.Document && (de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
linkDoc && makeLink(linkDoc);
}
@@ -668,8 +632,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
+ toggleLockPosition = (): void => {
+ this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
+ }
+
+ @undoBatch
+ @action
makeIntoPortal = async () => {
- const portalLink = DocListCast(this.Document.links).find(d => d.anchor1 === this.props.Document);
+ const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
if (!portalLink) {
const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), title: StrCast(this.props.Document.title) + ".portal" });
DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to");
@@ -680,9 +650,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
- toggleBackground = (temporary: boolean): void => {
- this.Document._overflow = temporary ? "visible" : "hidden";
- this.Document.isBackground = !temporary ? !this.Document.isBackground : (this.Document.isBackground ? undefined : true);
+ toggleBackground = () => {
+ this.Document.isBackground = (this.Document.isBackground ? undefined : true);
+ this.Document._overflow = this.Document.isBackground ? "visible" : undefined;
if (this.Document.isBackground) {
this.props.bringToFront(this.props.Document, true);
this.props.Document[DataSym][Doc.LayoutFieldKey(this.Document) + "-nativeWidth"] = this.Document[WidthSym]();
@@ -690,41 +660,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- @undoBatch
- @action
- toggleLockPosition = (): void => {
- this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
- }
-
- @undoBatch
- @action
- setAcl = (acl: SharingPermissions) => {
- this.dataDoc.ACL = this.props.Document.ACL = acl;
- DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
- if (d.author === Doc.CurrentUserEmail) d.ACL = acl;
- const data = d[DataSym];
- if (data && data.author === Doc.CurrentUserEmail) data.ACL = acl;
- });
- }
-
- @undoBatch
- @action
- testAcl = (acl: SharingPermissions) => {
- this.dataDoc.author = this.props.Document.author = "ADMIN";
- this.dataDoc.ACL = this.props.Document.ACL = acl;
- DocListCast(this.dataDoc[Doc.LayoutFieldKey(this.dataDoc)]).map(d => {
- if (d.author === Doc.CurrentUserEmail) {
- d.author = "ADMIN";
- d.ACL = acl;
- }
- const data = d[DataSym];
- if (data && data.author === Doc.CurrentUserEmail) {
- data.author = "ADMIN";
- data.ACL = acl;
- }
- });
- }
-
@action
onContextMenu = async (e: React.MouseEvent | Touch): Promise<void> => {
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
@@ -754,10 +689,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, self: this.rootDoc }), icon: "sticky-note" }));
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
- const appearance = cm.findByDescription("Appearance...");
+ const appearance = cm.findByDescription("UI Controls...");
const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
- !appearance && cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "compass" });
+ //DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
+ !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
const options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
@@ -770,11 +706,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(self, "${this.Document.layoutKey}")`), icon: "window-restore" });
onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
- onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: this.toggleFollowInPlace, icon: "concierge-bell" });
- onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link on Right", event: this.toggleFollowOnRight, icon: "concierge-bell" });
- onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: this.toggleLinkButtonBehavior, icon: "concierge-bell" });
+ onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "concierge-bell" });
+ !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("onRight", false, false), icon: "concierge-bell" });
+ onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "concierge-bell" });
+ onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "snowflake" });
onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
- !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, addDivider: true, subitems: onClicks, icon: "hand-point-right" });
const funcs: ContextMenuProps[] = [];
if (this.layoutDoc.onDragStart) {
@@ -786,16 +723,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const more = cm.findByDescription("More...");
const moreItems = more && "subitems" in more ? more.subitems : [];
- moreItems.push({
- description: "Download document", icon: "download", event: async () => {
- Doc.Zip(this.props.Document);
- // const a = document.createElement("a");
- // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
- // a.href = url;
- // a.download = `DocExport-${this.props.Document[Id]}.zip`;
- // a.click();
- }
- });
+ moreItems.push({ description: "Download document", icon: "download", event: async () => Doc.Zip(this.props.Document) });
if (!Doc.UserDoc().noviceMode) {
moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
@@ -807,190 +735,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
}
- GetEffectiveAcl(this.props.Document) === AclEdit && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "external-link-alt" });
!more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
cm.moveAfter(cm.findByDescription("More...")!, cm.findByDescription("OnClick...")!);
const help = cm.findByDescription("Help...");
const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
- //!Doc.UserDoc().novice && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
- !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+ helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }), "onRight"), icon: "keyboard" });
+ helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "layer-group" });
+ helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
- // const existingAcls = cm.findByDescription("Privacy...");
- // const aclItems: ContextMenuProps[] = existingAcls && "subitems" in existingAcls ? existingAcls.subitems : [];
- // aclItems.push({ description: "Make Add Only", event: () => this.setAcl(SharingPermissions.Add), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Read Only", event: () => this.setAcl(SharingPermissions.View), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Private", event: () => this.setAcl(SharingPermissions.None), icon: "concierge-bell" });
- // aclItems.push({ description: "Make Editable", event: () => this.setAcl(SharingPermissions.Edit), icon: "concierge-bell" });
- // aclItems.push({ description: "Test Private", event: () => this.testAcl(SharingPermissions.None), icon: "concierge-bell" });
- // aclItems.push({ description: "Test Readonly", event: () => this.testAcl(SharingPermissions.View), icon: "concierge-bell" });
- // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
-
- // cm.addItem({ description: `${getPlaygroundMode() ? "Disable" : "Enable"} playground mode`, event: togglePlaygroundMode, icon: "concierge-bell" });
-
- // const recommender_subitems: ContextMenuProps[] = [];
-
- // recommender_subitems.push({
- // description: "Internal recommendations",
- // event: () => this.recommender(),
- // icon: "brain"
- // });
-
- // const ext_recommender_subitems: ContextMenuProps[] = [];
-
- // ext_recommender_subitems.push({
- // description: "arXiv",
- // event: () => this.externalRecommendation("arxiv"),
- // icon: "brain"
- // });
- // ext_recommender_subitems.push({
- // description: "Bing",
- // event: () => this.externalRecommendation("bing"),
- // icon: "brain"
- // });
-
- // recommender_subitems.push({
- // description: "External recommendations",
- // subitems: ext_recommender_subitems,
- // icon: "brain"
- // });
-
-
- //moreItems.push({ description: "Recommender System", subitems: recommender_subitems, icon: "brain" });
- //moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
- //moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
-
- // runInAction(() => {
- // const setWriteMode = (mode: DocServer.WriteMode) => {
- // DocServer.AclsMode = mode;
- // const mode1 = mode;
- // const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
- // DocServer.setFieldWriteMode("x", mode1);
- // DocServer.setFieldWriteMode("y", mode1);
- // DocServer.setFieldWriteMode("_width", mode1);
- // DocServer.setFieldWriteMode("_height", mode1);
-
- // DocServer.setFieldWriteMode("_panX", mode2);
- // DocServer.setFieldWriteMode("_panY", mode2);
- // DocServer.setFieldWriteMode("scale", mode2);
- // DocServer.setFieldWriteMode("_viewType", mode2);
- // };
- // const aclsMenu: ContextMenuProps[] = [];
- // aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
- // aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
- // aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
- // aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
- // cm.addItem({ description: "Collaboration ...", subitems: aclsMenu, icon: "share" });
- // });
runInAction(() => {
if (!this.topMost && !(e instanceof Touch)) {
- // DocumentViews should stop propagation of this event
- e.stopPropagation();
+ e.stopPropagation(); // DocumentViews should stop propagation of this event
}
cm.displayMenu(e.pageX - 15, e.pageY - 15);
!SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false);
});
- const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), "");
- const item = ({
- description: `path: ${path}`, event: () => {
- if (this.props.LibraryPath !== emptyPath) {
- this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
- Doc.linkFollowHighlight(this.props.Document);
- } else {
- Doc.AddDocToList(Doc.GetProto(Doc.UserDoc().myCatalog as Doc), "data", this.props.Document[DataSym]);
- }
- }, icon: "check"
- });
- //cm.addItem(item);
- }
-
- recommender = async () => {
- if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
- const documents: Doc[] = [];
- const allDocs = await SearchUtil.GetAllDocs();
- // clears internal representation of documents as vectors
- ClientRecommender.Instance.reset_docs();
- //ClientRecommender.Instance.arxivrequest("electrons");
- await Promise.all(allDocs.map((doc: Doc) => {
- let isMainDoc: boolean = false;
- const dataDoc = Doc.GetProto(doc);
- if (doc.type === DocumentType.RTF) {
- if (dataDoc === Doc.GetProto(this.props.Document)) {
- isMainDoc = true;
- }
- if (!documents.includes(dataDoc)) {
- documents.push(dataDoc);
- const extdoc = doc.data_ext as Doc;
- return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc);
- }
- }
- if (doc.type === DocumentType.IMG) {
- if (dataDoc === Doc.GetProto(this.props.Document)) {
- isMainDoc = true;
- }
- if (!documents.includes(dataDoc)) {
- documents.push(dataDoc);
- const extdoc = doc.data_ext as Doc;
- return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, true, "", isMainDoc, true);
- }
- }
- }));
- const doclist = ClientRecommender.Instance.computeSimilarities("cosine");
- const recDocs: { preview: Doc, score: number }[] = [];
- // tslint:disable-next-line: prefer-for-of
- for (let i = 0; i < doclist.length; i++) {
- recDocs.push({ preview: doclist[i].actualDoc, score: doclist[i].score });
- }
-
- const data = recDocs.map(unit => {
- unit.preview.score = unit.score;
- return unit.preview;
- });
-
- console.log(recDocs.map(doc => doc.score));
-
- const title = `Showing ${data.length} recommendations for "${StrCast(this.props.Document.title)}"`;
- const recommendations = Docs.Create.RecommendationsDocument(data, { title });
- recommendations.documentIconHeight = 150;
- recommendations.sourceDoc = this.props.Document;
- recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document;
- this.props.addDocTab(recommendations, "onRight");
-
- // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY);
- }
-
- @action
- externalRecommendation = async (api: string) => {
- if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
- ClientRecommender.Instance.reset_docs();
- const doc = Doc.GetDataDoc(this.props.Document);
- const extdoc = doc.data_ext as Doc;
- const recs_and_kps = await ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc, false, api);
- let recs: any;
- let kps: any;
- if (recs_and_kps) {
- recs = recs_and_kps.recs;
- kps = recs_and_kps.keyterms;
- }
- else {
- console.log("recommender system failed :(");
- return;
- }
- console.log("ibm keyterms: ", kps.toString());
- const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("href")];
- const bodies: Doc[] = [];
- const titles = recs.title_vals;
- const urls = recs.url_vals;
- for (let i = 0; i < 5; i++) {
- const body = Docs.Create.FreeformDocument([], { title: titles[i] });
- body.href = urls[i];
- bodies.push(body);
- }
- this.props.addDocTab(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), "onRight");
- this._showKPQuery = true;
- this._queries = kps.toString();
}
// does Document set a layout prop
@@ -1020,6 +784,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return this.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
}
childScaling = () => (this.layoutDoc._fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
+ @computed.struct get linkOffset() { return [-15, 0]; }
@computed get contents() {
TraceMobx();
return (<div style={{ position: "absolute", width: "100%", height: "100%" }}>
@@ -1062,13 +827,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
layoutKey={this.finalLayoutKey} />
{this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
{/* {this.allAnchors} */}
- {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.hideLinkButton || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />}
+ {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.layoutDoc.isLinkButton || this.layoutDoc.hideLinkButton || this.props.dontRegisterView ? (null) :
+ <DocumentLinksButton View={this} links={this.allLinks} Offset={this.linkOffset} />}
</div>
);
}
// used to decide whether a link anchor view should be created or not.
- // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
+ // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
// would be good to generalize this some way.
isNonTemporalLink = (linkDoc: Doc) => {
const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc;
@@ -1076,7 +842,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
}
-
@observable _link: Opt<Doc>; // see DocumentButtonBar for explanation of how this works
makeLink = () => this._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
@@ -1085,13 +850,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
anchorPanelWidth = () => this.props.PanelWidth() || 1;
anchorPanelHeight = () => this.props.PanelHeight() || 1;
- @computed get allAnchors() {
+ @computed.struct get directLinks() { return LinkManager.Instance.getAllDirectLinks(this.Document); }
+ @computed.struct get allLinks() { return DocListCast(this.Document.links); }
+ @computed.struct get allAnchors() {
TraceMobx();
if (this.props.LayoutTemplateString?.includes("LinkAnchorBox")) return null;
return (this.props.treeViewDoc && this.props.LayoutTemplateString) || // render nothing for: tree view anchor dots
this.layoutDoc.presBox || // presentationbox nodes
+ this.rootDoc.type === DocumentType.LINK ||
this.props.dontRegisterView ? (null) : // view that are not registered
- DocUtils.FilterDocs(LinkManager.Instance.getAllDirectLinks(this.Document), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+ DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
<DocumentView {...this.props} key={i + 1}
Document={d}
ContainingCollectionView={this.props.ContainingCollectionView}
@@ -1164,7 +932,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
}
}
- @observable _animateScalingTo = 0;
+
switchViews = action((custom: boolean, view: string) => {
this._animateScalingTo = 0.1; // shrink doc
setTimeout(action(() => {
@@ -1178,7 +946,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return (this.Document.isBackground !== undefined || this.isSelected(false)) &&
((this.Document.type === DocumentType.COL && this.Document._viewType !== CollectionViewType.Pile) || this.Document.type === DocumentType.IMG) &&
this.props.renderDepth > 0 && !this.props.treeViewDoc ?
- <div className="documentView-lock" onClick={() => this.toggleBackground(true)}>
+ <div className="documentView-lock" onClick={this.toggleBackground}>
<FontAwesomeIcon icon={this.Document.isBackground ? "unlock" : "lock"} style={{ color: this.Document.isBackground ? "red" : undefined }} size="lg" />
</div>
: (null);
@@ -1202,7 +970,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear && this.props.Document.type !== DocumentType.INK;
highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
- return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
+ const topmost = this.topMost ? "-topmost" : "";
+ return <div className={`documentView-node${topmost}`}
id={this.props.Document[Id]}
ref={this._mainCont} onKeyDown={this.onKeyDown}
onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
@@ -1244,7 +1013,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.innards}
{this.renderLock()}
</div>;
- { this._showKPQuery ? <KeyphraseQueryView keyphrases={this._queries}></KeyphraseQueryView> : undefined; }
}
}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index ab34e13b0..2611d2ca7 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -67,7 +67,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
background: StrCast(refLayout._backgroundColor, StrCast(refLayout.backgroundColor)),
boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined
}}>
- <FontAwesomeIcon className="fontIconBox-icon" icon={this.dataDoc.icon as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
+ <FontAwesomeIcon className="fontIconBox-icon" icon={StrCast(this.dataDoc.icon, "user") as any} color={StrCast(this.layoutDoc.color, this._foregroundColor)} size="sm" />
{!this.rootDoc.title ? (null) : <div className="fontIconBox-label" style={{ width: this.rootDoc.label ? "max-content" : undefined }}> {StrCast(this.rootDoc.label, StrCast(this.rootDoc.title).substring(0, 6))} </div>}
</button>;
return !this.layoutDoc.toolTip ? button :
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5f689624c..d668d332b 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -66,7 +66,7 @@ const uploadIcons = {
@observer
export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
- protected multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
+ protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _dropDisposer?: DragManager.DragDropDisposer;
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index d4ab70200..be6292bb6 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -49,14 +49,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch
const bounds = cdiv.getBoundingClientRect();
const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
- const dragdist = Math.sqrt((pt[0] - down[0]) * (pt[0] - down[0]) + (pt[1] - down[1]) * (pt[1] - down[1]));
if (separation > 100) {
const dragData = new DragManager.DocumentDragData([this.rootDoc]);
dragData.dropAction = "alias";
dragData.removeDropProperties = ["anchor1_x", "anchor1_y", "anchor2_x", "anchor2_y", "isLinkButton"];
- DragManager.StartDocumentDrag([this._ref.current!], dragData, down[0], down[1]);
+ DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]);
return true;
- } else if (dragdist > separation) {
+ } else {
this.rootDoc[this.fieldKey + "_x"] = (pt[0] - bounds.left) / bounds.width * 100;
this.rootDoc[this.fieldKey + "_y"] = (pt[1] - bounds.top) / bounds.height * 100;
}
diff --git a/src/client/views/nodes/LinkCreatedBox.tsx b/src/client/views/nodes/LinkCreatedBox.tsx
deleted file mode 100644
index 648ae23c8..000000000
--- a/src/client/views/nodes/LinkCreatedBox.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
-import "./LinkCreatedBox.scss";
-import { observable, action } from "mobx";
-import { Fade } from "@material-ui/core";
-
-
-@observer
-export class LinkCreatedBox extends React.Component<{}> {
-
- @observable public static linkCreated: boolean = false;
- @observable public static popupX: number = 500;
- @observable public static popupY: number = 150;
-
- @action
- public static changeLinkCreated = () => {
- LinkCreatedBox.linkCreated = !LinkCreatedBox.linkCreated;
- }
-
- render() {
- return <Fade in={LinkCreatedBox.linkCreated}>
- <div className="linkCreatedBox-fade"
- style={{
- left: LinkCreatedBox.popupX ? LinkCreatedBox.popupX : 500,
- top: LinkCreatedBox.popupY ? LinkCreatedBox.popupY : 150,
- }}>Link Created</div>
- </Fade>;
- }
-} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 06e8d30d1..d8fe47f4e 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -4,7 +4,7 @@ import "./LinkDescriptionPopup.scss";
import { observable, action } from "mobx";
import { EditableView } from "../EditableView";
import { LinkManager } from "../../util/LinkManager";
-import { LinkCreatedBox } from "./LinkCreatedBox";
+import { TaskCompletionBox } from "./TaskCompletedBox";
@observer
@@ -31,7 +31,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
onClick = (e: PointerEvent) => {
if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
LinkDescriptionPopup.descriptionPopup = false;
- LinkCreatedBox.linkCreated = false;
+ TaskCompletionBox.taskCompleted = false;
}
}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index bc43cd473..1a5edc1d9 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -32,7 +32,7 @@ const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema);
export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) {
private dropDisposer?: DragManager.DragDropDisposer;
- protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); }
private _overlayDisposer?: () => void;
private _caretPos = 0;
diff --git a/src/client/views/nodes/LinkCreatedBox.scss b/src/client/views/nodes/TaskCompletedBox.scss
index 3cbd38b55..80b750b39 100644
--- a/src/client/views/nodes/LinkCreatedBox.scss
+++ b/src/client/views/nodes/TaskCompletedBox.scss
@@ -1,7 +1,6 @@
-.linkCreatedBox-fade {
+.taskCompletedBox-fade {
border: 1px solid rgb(100, 100, 100);
-
width: auto;
position: absolute;
diff --git a/src/client/views/nodes/TaskCompletedBox.tsx b/src/client/views/nodes/TaskCompletedBox.tsx
new file mode 100644
index 000000000..89602f219
--- /dev/null
+++ b/src/client/views/nodes/TaskCompletedBox.tsx
@@ -0,0 +1,32 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { makeInterface } from "../../../fields/Schema";
+import "./TaskCompletedBox.scss";
+import { observable, action } from "mobx";
+import { Fade } from "@material-ui/core";
+
+
+@observer
+export class TaskCompletionBox extends React.Component<{}> {
+
+ @observable public static taskCompleted: boolean = false;
+ @observable public static popupX: number = 500;
+ @observable public static popupY: number = 150;
+ @observable public static textDisplayed: string;
+
+ @action
+ public static toggleTaskCompleted = () => {
+ TaskCompletionBox.taskCompleted = !TaskCompletionBox.taskCompleted;
+ }
+
+ render() {
+ return <Fade in={TaskCompletionBox.taskCompleted}>
+ <div className="taskCompletedBox-fade"
+ style={{
+ left: TaskCompletionBox.popupX ? TaskCompletionBox.popupX : 500,
+ top: TaskCompletionBox.popupY ? TaskCompletionBox.popupY : 150,
+ }}>{TaskCompletionBox.textDisplayed}</div>
+ </Fade>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 4623444b9..875142169 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,135 +1,155 @@
@import "../globalCssVariables.scss";
-.webBox-container, .webBox-container-dragging {
- transform-origin: top left;
- width: 100%;
- height: 100%;
+.webBox {
+ height:100%;
+ position: relative;
+ display: flex;
- .webBox-htmlSpan {
+ .pdfViewerDash-dragAnnotationBox {
+ position:absolute;
+ background-color: transparent;
+ opacity: 0.1;
+ }
+ .webBox-annotationLayer {
position: absolute;
+ transform-origin: left top;
top: 0;
- left: 0;
- }
- .webBox-cont {
+ width: 100%;
pointer-events: none;
+ mix-blend-mode: multiply; // bcz: makes text fuzzy!
}
- .webBox-cont, .webBox-cont-interactive {
- padding: 0vw;
+ .webBox-annotationBox {
position: absolute;
- top: 0;
- left: 0;
+ background-color: rgba(245, 230, 95, 0.616);
+ }
+ .webBox-container {
+ transform-origin: top left;
width: 100%;
height: 100%;
- transform-origin: top left;
- overflow: auto;
- .webBox-iframe {
+
+ .webBox-htmlSpan {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+ .webBox-cont {
+ pointer-events: none;
+ }
+ .webBox-cont, .webBox-cont-interactive {
+ padding: 0vw;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ transform-origin: top left;
+ overflow: auto;
+ .webBox-iframe {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top:0;
+ }
+ }
+ .webBox-cont-interactive {
+ span {
+ user-select: text !important;
+ }
+ }
+ .webBox-outerContent {
width: 100%;
height: 100%;
position: absolute;
- top:0;
+ top: 0;
+ left: 0;
+ overflow: auto;
}
- }
- .webBox-cont-interactive {
- span {
- user-select: text !important;
+ div.webBox-outerContent::-webkit-scrollbar-thumb {
+ display:none;
}
}
- .webBox-outerContent {
+
+
+ .webBox-overlay {
width: 100%;
height: 100%;
position: absolute;
- top: 0;
- left: 0;
- overflow: auto;
- .webBox-innerContent {
- width:100%;
- }
- }
- div.webBox-outerContent::-webkit-scrollbar-thumb {
- display:none;
}
-}
-
-
-.webBox-overlay {
- width: 100%;
- height: 100%;
- position: absolute;
-}
-.webBox-buttons {
- margin-left: 44;
- background:lightGray;
- width: 100%;
-}
-.webBox-freeze {
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 5px;
- width: 30px;
-}
-
-.webBox-urlEditor {
- position: relative;
- opacity: 0.9;
- z-index: 9001;
- transition: top .5s;
-
- .urlEditor {
- display: grid;
- grid-template-columns: 1fr auto;
- padding-bottom: 10px;
- overflow: hidden;
-
- .editorBase {
- display: flex;
-
- .editor-collapse {
- transition: all .5s, opacity 0.3s;
- position: absolute;
- width: 40px;
- transform-origin: top left;
- }
+ .webBox-buttons {
+ margin-left: 44;
+ background:lightGray;
+ width: 100%;
+ }
+ .webBox-freeze {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 5px;
+ width: 30px;
+ }
- .switchToText {
- color: $main-accent;
+ .webBox-urlEditor {
+ position: relative;
+ opacity: 0.9;
+ z-index: 9001;
+ transition: top .5s;
+
+ .urlEditor {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ padding-bottom: 10px;
+ overflow: hidden;
+
+ .editorBase {
+ display: flex;
+
+ .editor-collapse {
+ transition: all .5s, opacity 0.3s;
+ position: absolute;
+ width: 40px;
+ transform-origin: top left;
+ }
+
+ .switchToText {
+ color: $main-accent;
+ }
+
+ .switchToText:hover {
+ color: $dark-color;
+ }
}
- .switchToText:hover {
- color: $dark-color;
+ button:hover {
+ transform: scale(1);
}
}
+ }
- button:hover {
- transform: scale(1);
- }
+ .webpage-urlInput {
+ padding: 12px 10px 11px 10px;
+ border: 0px;
+ color: grey;
+ letter-spacing: 2px;
+ outline-color: black;
+ background: rgb(238, 238, 238);
+ width: 100%;
+ margin-right: 10px;
+ height: 100%;
}
-}
-
-.webpage-urlInput {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: grey;
- letter-spacing: 2px;
- outline-color: black;
- background: rgb(238, 238, 238);
- width: 100%;
- margin-right: 10px;
- height: 100%;
-}
-
-.touch-iframe-overlay {
- width: 100%;
- height: 100%;
- position: absolute;
-
- .indicator {
+
+ .touch-iframe-overlay {
+ width: 100%;
+ height: 100%;
position: absolute;
- &.active {
- background-color: rgba(0, 0, 0, 0.1);
+ .indicator {
+ position: absolute;
+
+ &.active {
+ background-color: rgba(0, 0, 0, 0.1);
+ }
}
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 39bb7c01d..d30f1499e 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,53 +1,70 @@
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faStickyNote, faPen, faMousePointer } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, observable, trace, IReactionDisposer, reaction, runInAction } from "mobx";
+import { faMousePointer, faPen, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, FieldResult, DocListCast } from "../../../fields/Doc";
+import { Dictionary } from "typescript-collections";
+import * as WebRequest from 'web-request';
+import { Doc, DocListCast, Opt } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
+import { Id } from "../../../fields/FieldSymbols";
import { HtmlField } from "../../../fields/HtmlField";
import { InkTool } from "../../../fields/InkField";
-import { makeInterface, listSpec } from "../../../fields/Schema";
-import { Cast, NumCast, BoolCast, StrCast } from "../../../fields/Types";
+import { List } from "../../../fields/List";
+import { listSpec, makeInterface } from "../../../fields/Schema";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { WebField } from "../../../fields/URLField";
-import { Utils, returnOne, emptyFunction, returnZero } from "../../../Utils";
-import { Docs } from "../../documents/Documents";
+import { TraceMobx } from "../../../fields/util";
+import { addStyleSheet, clearStyleSheetRules, emptyFunction, returnOne, returnZero, Utils, returnTrue } from "../../../Utils";
+import { Docs, DocUtils } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
+import { undoBatch } from "../../util/UndoManager";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
+import Annotation from "../pdf/Annotation";
+import PDFMenu from "../pdf/PDFMenu";
+import { PdfViewerMarquee } from "../pdf/PDFViewer";
import { FieldView, FieldViewProps } from './FieldView';
import "./WebBox.scss";
+import "../pdf/PDFViewer.scss";
import React = require("react");
-import * as WebRequest from 'web-request';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { undoBatch } from "../../util/UndoManager";
-import { List } from "../../../fields/List";
const htmlToText = require("html-to-text");
-library.add(faStickyNote);
-
type WebDocument = makeInterface<[typeof documentSchema]>;
const WebDocument = makeInterface(documentSchema);
@observer
export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) {
+ private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ static _annotationStyle: any = addStyleSheet();
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ private _startX: number = 0;
+ private _startY: number = 0;
+ @observable private _marqueeX: number = 0;
+ @observable private _marqueeY: number = 0;
+ @observable private _marqueeWidth: number = 0;
+ @observable private _marqueeHeight: number = 0;
+ @observable private _marqueeing: boolean = false;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); }
get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; }
set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; }
@observable private _url: string = "hello";
@observable private _pressX: number = 0;
@observable private _pressY: number = 0;
+ @observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ private _selectionReactionDisposer?: IReactionDisposer;
+ private _scrollReactionDisposer?: IReactionDisposer;
+ private _moveReactionDisposer?: IReactionDisposer;
private _keyInput = React.createRef<HTMLInputElement>();
private _longPressSecondsHack?: NodeJS.Timeout;
private _outerRef = React.createRef<HTMLDivElement>();
private _iframeRef = React.createRef<HTMLIFrameElement>();
private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
private _iframeDragRef = React.createRef<HTMLDivElement>();
- private _reactionDisposer?: IReactionDisposer;
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void);
iframeLoaded = action((e: any) => {
@@ -60,21 +77,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc._scrollTop);
iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc._scrollLeft);
}
- this._reactionDisposer?.();
- this._reactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }),
- ({ x, y }) => {
- if (y !== undefined) {
- this._outerRef.current!.scrollTop = y;
- this.layoutDoc._scrollY = undefined;
- }
- if (x !== undefined) {
- this._outerRef.current!.scrollLeft = x;
- this.layoutDoc.scrollX = undefined;
- }
- },
+ this._scrollReactionDisposer?.();
+ this._scrollReactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }),
+ ({ x, y }) => this.updateScroll(x, y),
{ fireImmediately: true }
);
});
+
+ updateScroll = (x: Opt<number>, y: Opt<number>) => {
+ if (y !== undefined) {
+ this._outerRef.current!.scrollTop = y;
+ this.layoutDoc._scrollY = undefined;
+ }
+ if (x !== undefined) {
+ this._outerRef.current!.scrollLeft = x;
+ this.layoutDoc.scrollX = undefined;
+ }
+ }
+
setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func;
iframedown = (e: PointerEvent) => {
this._setPreviewCursor?.(e.screenX, e.screenY, false);
@@ -89,6 +109,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
runInAction(() => this._url = urlField?.url.toString() || "");
+ this._moveReactionDisposer = reaction(() => this.layoutDoc.x || this.layoutDoc.y,
+ () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop));
+
+ this._selectionReactionDisposer = reaction(() => this.props.isSelected(),
+ selected => {
+ if (!selected) {
+ this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
+ this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, []));
+ PDFMenu.Instance.fadeOut(true);
+ }
+ },
+ { fireImmediately: true });
+
document.addEventListener("pointerup", this.onLongPressUp);
document.addEventListener("pointermove", this.onLongPressMove);
const field = Cast(this.rootDoc[this.props.fieldKey], WebField);
@@ -112,7 +145,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
}
componentWillUnmount() {
- this._reactionDisposer?.();
+ this._moveReactionDisposer?.();
+ this._selectionReactionDisposer?.();
+ this._scrollReactionDisposer?.();
document.removeEventListener("pointerup", this.onLongPressUp);
document.removeEventListener("pointermove", this.onLongPressMove);
this._iframeRef.current?.contentDocument?.removeEventListener('pointerdown', this.iframedown);
@@ -267,11 +302,14 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
);
}
+
@action
toggleCollapse = () => {
this._collapsed = !this._collapsed;
}
+
+
_ignore = 0;
onPreWheel = (e: React.WheelEvent) => {
this._ignore = e.timeStamp;
@@ -284,6 +322,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
e.stopPropagation();
}
}
+
onPostWheel = (e: React.WheelEvent) => {
if (this._ignore !== e.timeStamp) {
e.stopPropagation();
@@ -399,7 +438,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
specificContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
+ funcs.push({ description: (this.layoutDoc.UseCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.UseCors = !this.layoutDoc.UseCors, icon: "snowflake" });
cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
@@ -430,6 +469,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
return (<>
<div className={"webBox-cont" + (this.props.isSelected() && Doc.GetSelectedTool() === InkTool.None && !decInteracting ? "-interactive" : "")}
+ style={{ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%" }}
onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
{view}
</div>;
@@ -444,60 +484,259 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
{this.urlEditor()}
</>);
}
+
+
+
+ @computed get allAnnotations() { return DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]); }
+ @computed get nonDocAnnotations() { return this.allAnnotations.filter(a => a.annotations); }
+
+ @undoBatch
+ @action
+ makeAnnotationDocument = (color: string): Opt<Doc> => {
+ if (this._savedAnnotations.size() === 0) return undefined;
+ const anno = this._savedAnnotations.values()[0][0];
+ const annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.Document, title: "Annotation on " + this.Document.title });
+ if (anno.style.left) annoDoc.x = parseInt(anno.style.left);
+ if (anno.style.top) annoDoc.y = NumCast(this.layoutDoc._scrollTop) + parseInt(anno.style.top);
+ if (anno.style.height) annoDoc._height = parseInt(anno.style.height);
+ if (anno.style.width) annoDoc._width = parseInt(anno.style.width);
+ anno.remove();
+ this._savedAnnotations.clear();
+ return annoDoc;
+ }
+ @computed get annotationLayer() {
+ TraceMobx();
+ return <div className="webBox-annotationLayer" style={{ height: NumCast(this.Document._nativeHeight) }} ref={this._annotationLayer}>
+ {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno =>
+ <Annotation {...this.props} focus={this.props.focus} dataDoc={this.dataDoc} fieldKey={this.props.fieldKey} anno={anno} key={`${anno[Id]}-annotation`} />)
+ }
+ </div>;
+ }
+ @action
+ createAnnotation = (div: HTMLDivElement, page: number) => {
+ if (this._annotationLayer.current) {
+ if (div.style.top) {
+ div.style.top = (parseInt(div.style.top)).toString();
+ }
+ this._annotationLayer.current.append(div);
+ div.style.backgroundColor = "#ACCEF7";
+ div.style.opacity = "0.5";
+ const savedPage = this._savedAnnotations.getValue(page);
+ if (savedPage) {
+ savedPage.push(div);
+ this._savedAnnotations.setValue(page, savedPage);
+ }
+ else {
+ this._savedAnnotations.setValue(page, [div]);
+ }
+ }
+ }
+
+ @action
+ highlight = (color: string) => {
+ // creates annotation documents for current highlights
+ const annotationDoc = this.makeAnnotationDocument(color);
+ annotationDoc && Doc.AddDocToList(this.props.Document, this.annotationKey, annotationDoc);
+ return annotationDoc;
+ }
+ /**
+ * This is temporary for creating annotations from highlights. It will
+ * start a drag event and create or put the necessary info into the drag event.
+ */
+ @action
+ startDrag = async (e: PointerEvent, ele: HTMLElement) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const clipDoc = Doc.MakeAlias(this.dataDoc);
+ clipDoc._fitWidth = true;
+ clipDoc._width = this.marqueeWidth();
+ clipDoc._height = this.marqueeHeight();
+ clipDoc._scrollTop = this.marqueeY();
+ const targetDoc = Docs.Create.TextDocument("", { _width: 125, _height: 125, title: "Note linked to " + this.props.Document.title });
+ Doc.GetProto(targetDoc).data = new List<Doc>([clipDoc]);
+ clipDoc.rootDocument = targetDoc;
+ targetDoc.layoutKey = "layout";
+ const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection
+ if (annotationDoc) {
+ DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, {
+ dragComplete: e => {
+ if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) {
+ DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation");
+ annotationDoc.isLinkButton = true;
+ }
+ }
+ });
+ }
+ }
+ @action
+ onMarqueeDown = (e: React.PointerEvent) => {
+ this._marqueeing = false;
+ if (!e.altKey && e.button === 0 && this.active(true)) {
+ // clear out old marquees and initialize menu for new selection
+ PDFMenu.Instance.StartDrag = this.startDrag;
+ PDFMenu.Instance.Highlight = this.highlight;
+ 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 as any)?.parentElement.className === "textLayer") {
+ // start selecting text if mouse down on textLayer spans
+ }
+ else if (this._mainCont.current) {
+ // set marquee x and y positions to the spatially transformed position
+ const boundingRect = this._mainCont.current.getBoundingClientRect();
+ const boundingHeight = (this.Document._nativeHeight || 1) / (this.Document._nativeWidth || 1) * boundingRect.width;
+ this._startX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1);
+ this._startY = (e.clientY - boundingRect.top) / boundingHeight * (this.Document._nativeHeight || 1);
+ this._marqueeHeight = this._marqueeWidth = 0;
+ this._marqueeing = true;
+ }
+ document.removeEventListener("pointermove", this.onSelectMove);
+ document.addEventListener("pointermove", this.onSelectMove);
+ document.removeEventListener("pointerup", this.onSelectEnd);
+ document.addEventListener("pointerup", this.onSelectEnd);
+ }
+ }
+ @action
+ onSelectMove = (e: PointerEvent): void => {
+ if (this._marqueeing && this._mainCont.current) {
+ // transform positions and find the width and height to set the marquee to
+ const boundingRect = this._mainCont.current.getBoundingClientRect();
+ const boundingHeight = (this.Document._nativeHeight || 1) / (this.Document._nativeWidth || 1) * boundingRect.width;
+ const curX = (e.clientX - boundingRect.left) / boundingRect.width * (this.Document._nativeWidth || 1);
+ const curY = (e.clientY - boundingRect.top) / boundingHeight * (this.Document._nativeHeight || 1);
+ this._marqueeWidth = curX - this._startX;
+ this._marqueeHeight = curY - this._startY;
+ this._marqueeX = Math.min(this._startX, this._startX + this._marqueeWidth);
+ this._marqueeY = Math.min(this._startY, this._startY + this._marqueeHeight);
+ this._marqueeWidth = Math.abs(this._marqueeWidth);
+ this._marqueeHeight = Math.abs(this._marqueeHeight);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ else if (e.target && (e.target as any).parentElement === this._mainCont.current) {
+ e.stopPropagation();
+ }
+ }
+
+ @action
+ onSelectEnd = (e: PointerEvent): void => {
+ clearStyleSheetRules(WebBox._annotationStyle);
+ this._savedAnnotations.clear();
+ if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
+ const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox");
+ if (marquees?.length) { // copy the marquee and convert it to a permanent annotation.
+ const style = (marquees[0] as HTMLDivElement).style;
+ const copy = document.createElement("div");
+ 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 as any).marqueeing = true;
+ copy.className = "webBox-annotationBox";
+ this.createAnnotation(copy, 0);
+ }
+
+ if (!e.ctrlKey) {
+ PDFMenu.Instance.Marquee = { left: this._marqueeX, top: this._marqueeY, width: this._marqueeWidth, height: this._marqueeHeight };
+ }
+ PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
+ }
+ //this._marqueeing = false;
+
+ 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;
+ PDFMenu.Instance.Highlight = this.highlight;
+ }
+ document.removeEventListener("pointermove", this.onSelectMove);
+ document.removeEventListener("pointerup", this.onSelectEnd);
+ }
+ marqueeWidth = () => this._marqueeWidth;
+ marqueeHeight = () => this._marqueeHeight;
+ marqueeX = () => this._marqueeX;
+ marqueeY = () => this._marqueeY;
+ marqueeing = () => this._marqueeing;
+ visibleHeiht = () => {
+ if (this._mainCont.current) {
+ const boundingRect = this._mainCont.current.getBoundingClientRect();
+ const scalin = (this.Document._nativeWidth || 0) / boundingRect.width;
+ return Math.min(boundingRect.height * scalin, this.props.PanelHeight() * scalin);
+ }
+ return this.props.PanelHeight();
+ }
scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
render() {
- return (<div className={`webBox-container`}
- style={{
- transform: `scale(${this.props.ContentScaling()})`,
- width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
- height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}%` : "100%",
- pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
- }}
- onContextMenu={this.specificContextMenu}>
- <base target="_blank" />
- {this.content}
- <div className={"webBox-outerContent"} ref={this._outerRef}
- style={{ pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none" }}
- onWheel={e => e.stopPropagation()}
- onScroll={e => {
- const iframe = this._iframeRef?.current?.contentDocument;
- const outerFrame = this._outerRef.current;
- if (iframe && outerFrame) {
- if (iframe.children[0].scrollTop !== outerFrame.scrollTop) {
- iframe.children[0].scrollTop = outerFrame.scrollTop;
- }
- if (iframe.children[0].scrollLeft !== outerFrame.scrollLeft) {
- iframe.children[0].scrollLeft = outerFrame.scrollLeft;
+ return (<div className="webBox" ref={this._mainCont} >
+ <div className={`webBox-container`}
+ style={{
+ position: undefined,
+ transform: `scale(${this.props.ContentScaling()})`,
+ width: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%",
+ height: Number.isFinite(this.props.ContentScaling()) ? `${100 / this.props.ContentScaling()}% ` : "100%",
+ pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
+ }}
+ onContextMenu={this.specificContextMenu}>
+ <base target="_blank" />
+ {this.content}
+ <div className={"webBox-outerContent"} ref={this._outerRef}
+ style={{
+ width: Number.isFinite(this.props.ContentScaling()) ? `${Math.max(100, 100 / this.props.ContentScaling())}% ` : "100%",
+ pointerEvents: this.layoutDoc.isAnnotating && !this.layoutDoc.isBackground ? "all" : "none"
+ }}
+ onWheel={e => e.stopPropagation()}
+ onPointerDown={this.onMarqueeDown}
+ onScroll={e => {
+ const iframe = this._iframeRef?.current?.contentDocument;
+ const outerFrame = this._outerRef.current;
+ if (iframe && outerFrame) {
+ if (iframe.children[0].scrollTop !== outerFrame.scrollTop) {
+ iframe.children[0].scrollTop = outerFrame.scrollTop;
+ }
+ if (iframe.children[0].scrollLeft !== outerFrame.scrollLeft) {
+ iframe.children[0].scrollLeft = outerFrame.scrollLeft;
+ }
}
- }
- //this._outerRef.current!.scrollTop !== this._scrollTop && (this._outerRef.current!.scrollTop = this._scrollTop)
- }}>
- <div className={"webBox-innerContent"} style={{ height: NumCast(this.layoutDoc.scrollHeight), width: 4000 }}>
- <CollectionFreeFormView {...this.props}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.props.PanelWidth}
- annotationsKey={this.annotationKey}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- focus={this.props.focus}
- setPreviewCursor={this.setPreviewCursor}
- isSelected={this.props.isSelected}
- isAnnotationOverlay={true}
- select={emptyFunction}
- active={this.active}
- ContentScaling={returnOne}
- whenActiveChanged={this.whenActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}
- CollectionView={undefined}
- ScreenToLocalTransform={this.scrollXf}
- renderDepth={this.props.renderDepth + 1}
- docFilters={this.props.docFilters}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
- </CollectionFreeFormView>
+ //this._outerRef.current!.scrollTop !== this._scrollTop && (this._outerRef.current!.scrollTop = this._scrollTop)
+ }}>
+ <div className={"webBox-innerContent"} style={{
+ height: NumCast(this.layoutDoc.scrollHeight),
+ pointerEvents: this.layoutDoc.isBackground ? "none" : undefined
+ }}>
+ <CollectionFreeFormView {...this.props}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ annotationsKey={this.annotationKey}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ VisibleHeight={this.visibleHeiht}
+ focus={this.props.focus}
+ setPreviewCursor={this.setPreviewCursor}
+ isSelected={this.props.isSelected}
+ isAnnotationOverlay={true}
+ select={emptyFunction}
+ active={this.active}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.scrollXf}
+ renderDepth={this.props.renderDepth + 1}
+ docFilters={this.props.docFilters}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
+ </CollectionFreeFormView>
+ </div>
</div>
- </div>
- </div >);
+ {this.annotationLayer}
+ <PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
+ </div >
+ </div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 8718bf329..958a37568 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -205,9 +205,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
{this._fieldKey}
</span>}
- {/* <div className="dashFieldView-fieldSpan"> */}
- {this.fieldValueContent}
- {/* </div> */}
+ {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent}
{!this._showEnumerables ? (null) : <div className="dashFieldView-enumerables" onPointerDown={this.onPointerDownEnumerables} />}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 38fa66d65..627c6e363 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "
import { ReplaceStep } from 'prosemirror-transform';
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclEdit, AclAdmin } from "../../../../fields/Doc";
import { documentSchema } from '../../../../fields/documentSchemas';
import applyDevTools = require("prosemirror-dev-tools");
import { removeMarkWithAttrs } from "./prosemirrorPatches";
@@ -223,7 +223,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
const tsel = this._editorView.state.selection.$from;
tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000)));
@@ -233,7 +232,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template
const json = JSON.stringify(state.toJSON());
let unchanged = true;
- if (GetEffectiveAcl(this.dataDoc) === AclEdit) {
+ const effectiveAcl = GetEffectiveAcl(this.dataDoc);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) {
this._applyingChange = true;
(curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())));
@@ -689,7 +689,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
);
this._disposers.editorState = reaction(
() => {
- if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
+ if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey + "-textTemplate"]) {
return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data;
}
return Cast(this.layoutDoc[this.props.fieldKey + "-textTemplate"], RichTextField, null)?.Data;
@@ -740,7 +740,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._disposers.search = reaction(() => this.rootDoc.searchMatch,
search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
- { fireImmediately: true });
+ { fireImmediately: this.rootDoc.searchMatch ? true : false });
this._disposers.record = reaction(() => this._recording,
() => {
@@ -993,7 +993,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
+ // !Doc.UserDoc().noviceMode && applyDevTools.applyDevTools(this._editorView);
const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field);
if (startupText) {
const { state: { tr }, dispatch } = this._editorView;
@@ -1010,10 +1010,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
FormattedTextBox.SelectOnLoadChar = "";
}
- (selectOnLoad /* || !rtfField?.Text*/) && this._editorView!.focus();
+ selectOnLoad && 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.
- if (!this._editorView!.state.storedMarks || !this._editorView!.state.storedMarks.some(mark => mark.type === schema.marks.user_mark)) {
- this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
+ if (!this._editorView!.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) {
+ this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ?? []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })];
}
}
getFont(font: string) {
@@ -1400,12 +1400,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
onDoubleClick={this.onDoubleClick}
>
<div className={`formattedTextBox-outer`} ref={this._scrollRef}
- style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }}
+ style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.active() ? "none" : undefined }}
onScroll={this.onscrolled} onDrop={this.ondrop} >
<div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget}
style={{
padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`,
- pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined
+ pointerEvents: !this.props.active() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : undefined) : undefined
}}
/>
</div>
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ef0fead4a..dc1d8a2c8 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -317,13 +317,12 @@ export class RichTextRules {
// create an inline view of a tag stored under the '#' field
new InputRule(
- new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_;\-0-9]*)\s$/),
+ new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/),
(state, match, start, end) => {
const tag = match[1];
if (!tag) return state.tr;
- const multiple = tag.split(";");
- this.Document[DataSym]["#"] = multiple.length > 1 ? new List(multiple) : tag;
- const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" });
+ this.Document[DataSym]["#" + tag] = ".";
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag });
return state.tr.deleteRange(start, end).insert(start, fieldView);
}),
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index f95f46104..bcd6f716b 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -40,7 +40,7 @@ export const marks: { [index: string]: MarkSpec } = {
return node.attrs.docref && node.attrs.title ?
["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] :
node.attrs.allLinks.length === 1 ?
- ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] :
+ ["a", { ...node.attrs, class: linkids, targetids, style: `text-decoration: ${linkids === " " ? "underline" : undefined}`, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] :
["div", { class: "prosemirror-anchor" },
["span", { class: "prosemirror-linkBtn" },
["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}` }, 0],
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 6592c488b..c3e1ae22f 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -84,11 +84,6 @@ export default class PDFMenu extends AntimodeMenu {
e.preventDefault();
}
- togglePin = action((e: React.MouseEvent) => {
- this.Pinned = !this.Pinned;
- !this.Pinned && (this.Highlighting = false);
- });
-
@action
highlightClicked = (e: React.MouseEvent) => {
if (!this.Highlight(this.highlightColor) && this.Pinned) {
@@ -161,8 +156,6 @@ export default class PDFMenu extends AntimodeMenu {
this.highlighter,
<button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
<FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
- <button key="4" className="antimodeMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /></button>,
] : [
<button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
<FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 30c51d9ca..c792df882 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -346,7 +346,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
scrollToAnnotation = (scrollToAnnotation: Doc) => {
if (scrollToAnnotation) {
- const offset = this.visibleHeight() / 2 * 96 / 72;
+ const offset = this.visibleHeight() / 2;
this._mainCont.current && smoothScroll(500, this._mainCont.current, NumCast(scrollToAnnotation.y) - offset);
Doc.linkFollowHighlight(scrollToAnnotation);
}
@@ -521,7 +521,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
if (this._marqueeing) {
if (this._marqueeWidth > 10 || this._marqueeHeight > 10) {
const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox");
- if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation.
+ if (marquees?.length) { // copy the marquee and convert it to a permanent annotation.
const style = (marquees[0] as HTMLDivElement).style;
const copy = document.createElement("div");
copy.style.left = style.left;
@@ -544,7 +544,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
else {
const sel = window.getSelection();
- if (sel && sel.type === "Range") {
+ if (sel?.type === "Range") {
const selRange = sel.getRangeAt(0);
this.createTextAnnotation(sel, selRange);
PDFMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -705,7 +705,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
marqueeX = () => this._marqueeX;
marqueeY = () => this._marqueeY;
marqueeing = () => this._marqueeing;
- visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96;
+ visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling();
contentZoom = () => this._zoomed;
render() {
TraceMobx();
@@ -726,7 +726,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
}
}
-interface PdfViewerMarqueeProps {
+export interface PdfViewerMarqueeProps {
isMarqueeing: () => boolean;
width: () => number;
height: () => number;
@@ -735,7 +735,7 @@ interface PdfViewerMarqueeProps {
}
@observer
-class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
+export class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> {
render() {
return !this.props.isMarqueeing() ? (null) : <div className="pdfViewerDash-dragAnnotationBox"
style={{
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 7e91a7761..267defa4b 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -17,11 +17,10 @@ import { RichTextField } from "./RichTextField";
import { ImageField, VideoField, WebField, AudioField, PdfField } from "./URLField";
import { DateField } from "./DateField";
import { listSpec } from "./Schema";
-import { ComputedField } from "./ScriptField";
+import { ComputedField, ScriptField } from "./ScriptField";
import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types";
-import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl } from "./util";
+import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl, SharingPermissions } from "./util";
import { LinkManager } from "../client/util/LinkManager";
-import { SharingPermissions } from "../client/util/SharingManager";
import JSZip = require("jszip");
import { saveAs } from "file-saver";
@@ -103,26 +102,28 @@ export const AclPrivate = Symbol("AclOwnerOnly");
export const AclReadonly = Symbol("AclReadOnly");
export const AclAddonly = Symbol("AclAddonly");
export const AclEdit = Symbol("AclEdit");
+export const AclAdmin = Symbol("AclAdmin");
export const UpdatingFromServer = Symbol("UpdatingFromServer");
-const CachedUpdates = Symbol("Cached updates");
+export const CachedUpdates = Symbol("Cached updates");
const AclMap = new Map<string, symbol>([
[SharingPermissions.None, AclPrivate],
[SharingPermissions.View, AclReadonly],
[SharingPermissions.Add, AclAddonly],
- [SharingPermissions.Edit, AclEdit]
+ [SharingPermissions.Edit, AclEdit],
+ [SharingPermissions.Admin, AclAdmin]
]);
export function fetchProto(doc: Doc) {
- // if (doc.author !== Doc.CurrentUserEmail) {
- untracked(() => {
- const permissions: { [key: string]: symbol } = {};
+ const permissions: { [key: string]: symbol } = {};
- Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
+ Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!);
- if (Object.keys(permissions).length) doc[AclSym] = permissions;
- });
- // }
+ if (Object.keys(permissions).length) doc[AclSym] = permissions;
+
+ if (GetEffectiveAcl(doc) === AclPrivate) {
+ runInAction(() => doc[FieldsSym](true));
+ }
if (doc.proto instanceof Promise) {
doc.proto.then(fetchProto);
@@ -142,7 +143,7 @@ export class Doc extends RefField {
has: (target, key) => GetEffectiveAcl(target) !== AclPrivate && key in target.__fields,
ownKeys: target => {
const obj = {} as any;
- if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fields);
+ if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys);
runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__);
return Object.keys(obj);
},
@@ -150,11 +151,11 @@ export class Doc extends RefField {
if (prop.toString() === "__LAYOUT__") {
return Reflect.getOwnPropertyDescriptor(target, prop);
}
- if (prop in target.__fields) {
+ if (prop in target.__fieldKeys) {
return {
configurable: true,//TODO Should configurable be true?
enumerable: true,
- value: target.__fields[prop]
+ value: 0//() => target.__fields[prop])
};
}
return Reflect.getOwnPropertyDescriptor(target, prop);
@@ -178,15 +179,23 @@ export class Doc extends RefField {
this.___fields = value;
for (const key in value) {
const field = value[key];
+ field && (this.__fieldKeys[key] = true);
if (!(field instanceof ObjectField)) continue;
field[Parent] = this[Self];
field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]);
}
}
+ private get __fieldKeys() { return this.___fieldKeys; }
+ private set __fieldKeys(value) {
+ this.___fieldKeys = value;
+ }
@observable
private ___fields: any = {};
+ @observable
+ private ___fieldKeys: any = {};
+
private [UpdatingFromServer]: boolean = false;
private [Update] = (diff: any) => {
@@ -195,7 +204,14 @@ export class Doc extends RefField {
private [Self] = this;
private [SelfProxy]: any;
- public [FieldsSym] = () => this.___fields;
+ public [FieldsSym] = (clear?: boolean) => {
+ if (clear) {
+ this.___fields = {};
+ this.___fieldKeys = {};
+ }
+ return this.___fields;
+ }
+ @observable
public [AclSym]: { [key: string]: symbol };
public [WidthSym] = () => NumCast(this[SelfProxy]._width);
public [HeightSym] = () => NumCast(this[SelfProxy]._height);
@@ -236,11 +252,18 @@ export class Doc extends RefField {
const fKey = key.substring(7);
const fn = async () => {
const value = await SerializationHelper.Deserialize(set[key]);
+ const prev = GetEffectiveAcl(this);
this[UpdatingFromServer] = true;
this[fKey] = value;
+ if (fKey.startsWith("ACL")) {
+ fetchProto(this);
+ }
this[UpdatingFromServer] = false;
+ if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) {
+ DocServer.GetRefField(this[Id], true);
+ }
};
- if (sameAuthor || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
+ if (sameAuthor || fKey.startsWith("ACL") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) {
delete this[CachedUpdates][fKey];
await fn();
} else {
@@ -564,6 +587,11 @@ export namespace Doc {
}
export async function Zip(doc: Doc) {
+ // const a = document.createElement("a");
+ // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ // a.href = url;
+ // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ // a.click();
const { clone, map } = await Doc.MakeClone(doc, true);
function replacer(key: any, value: any) {
if (["cloneOf", "context", "cursors"].includes(key)) return undefined;
@@ -575,6 +603,7 @@ export namespace Doc {
return { fieldId: value[Id], __type: "proxy" };
}
}
+ else if (value instanceof ScriptField) return { script: value.script, __type: "script" };
else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" };
else if (value instanceof ImageField) return { url: value.url.href, __type: "image" };
else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" };
@@ -892,7 +921,6 @@ export namespace Doc {
return doc;
}
export function UnBrushDoc(doc: Doc) {
-
if (!doc || GetEffectiveAcl(doc) === AclPrivate || GetEffectiveAcl(Doc.GetProto(doc)) === AclPrivate) return doc;
brushManager.BrushedDoc.delete(doc);
brushManager.BrushedDoc.delete(Doc.GetProto(doc));
@@ -1212,7 +1240,7 @@ Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); });
Scripting.addGlobal(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); });
Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); });
Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); });
-Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); });
+Scripting.addGlobal(function copyField(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; });
Scripting.addGlobal(function aliasDocs(field: any) { return Doc.aliasDocs(field); });
Scripting.addGlobal(function docList(field: any) { return DocListCast(field); });
Scripting.addGlobal(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); });
diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts
index 98ef3e087..23ac50f74 100644
--- a/src/fields/Schema.ts
+++ b/src/fields/Schema.ts
@@ -31,7 +31,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
}
const proto = new Proxy({}, {
get(target: any, prop, receiver) {
- const field = receiver.doc[prop];
+ const field = receiver.doc?.[prop];
if (prop in schema) {
const desc = prop === "proto" ? Doc : (schema as any)[prop]; // bcz: proto doesn't appear in schemas ... maybe it should?
if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec
@@ -52,7 +52,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu
return field;
},
set(target: any, prop, value, receiver) {
- receiver.doc[prop] = value;
+ receiver.doc && (receiver.doc[prop] = value); // receiver.doc may be undefined as the result of a change in ACLs
return true;
}
});
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index bd08b2f32..f55483a5b 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -183,6 +183,10 @@ Scripting.addGlobal(function getIndexVal(list: any[], index: number) {
return list.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any);
}, "returns the value at a given index of a list", "(list: any[], index: number)");
+Scripting.addGlobal(function makeScript(script: string) {
+ return ScriptField.MakeScript(script);
+}, "returns the value at a given index of a list", "(list: any[], index: number)");
+
export namespace ComputedField {
let useComputed = true;
export function DisableComputedFields() {
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index ddffb56c3..8cf8f47b7 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -90,6 +90,7 @@ export const documentSchema = createSchema({
followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, )
hideLinkButton: "boolean", // whether the blue link counter button should be hidden
hideAllLinks: "boolean", // whether all individual blue anchor dots should be hidden
+ linkDisplay: "boolean", // whether a link connection should be shown between link anchor endpoints.
isInPlaceContainer: "boolean",// whether the marked object will display addDocTab() calls that target "inPlace" destinations
isLinkButton: "boolean", // whether document functions as a link follow button to follow the first link on the document when clicked
isBackground: "boolean", // whether document is a background element and ignores input events (can only select with marquee)
diff --git a/src/fields/util.ts b/src/fields/util.ts
index ef66d9633..a62795e64 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,15 +1,15 @@
import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, fetchProto, DataSym, DocListCast } from "./Doc";
+import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAddonly, AclSym, CachedUpdates, DataSym, DocListCast, AclAdmin, FieldsSym, HeightSym, WidthSym } from "./Doc";
import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField, PrefetchProxy } from "./Proxy";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action, trace } from "mobx";
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
+import { Parent, OnUpdate, Update, Id, SelfProxy, Self, HandleUpdate } from "./FieldSymbols";
import { DocServer } from "../client/DocServer";
import { ComputedField } from "./ScriptField";
import { ScriptCast, StrCast } from "./Types";
-import { SharingPermissions } from "../client/util/SharingManager";
+import { returnZero } from "../Utils";
function _readOnlySetter(): never {
@@ -67,16 +67,21 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
delete curValue[Parent];
delete curValue[OnUpdate];
}
+
+ const effectiveAcl = GetEffectiveAcl(target);
+
const writeMode = DocServer.getFieldWriteMode(prop as string);
const fromServer = target[UpdatingFromServer];
const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
- const writeToDoc = sameAuthor || GetEffectiveAcl(target) === AclEdit || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer = (sameAuthor || GetEffectiveAcl(target) === AclEdit || writeMode === DocServer.WriteMode.Default) && !playgroundMode;
+ const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
+ const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode === DocServer.WriteMode.Default) && !playgroundMode;
if (writeToDoc) {
if (value === undefined) {
+ target.__fieldKeys && (delete target.__fieldKeys[prop]);
delete target.__fields[prop];
} else {
+ target.__fieldKeys && (target.__fieldKeys[prop] = true);
target.__fields[prop] = value;
}
//if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
@@ -126,12 +131,20 @@ export function setGroups(groups: string[]) {
currentUserGroups = groups;
}
+export enum SharingPermissions {
+ Admin = "Admin",
+ Edit = "Can Edit",
+ Add = "Can Add",
+ View = "Can View",
+ None = "Not Shared"
+}
+
export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number): symbol {
- if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclEdit;
+ if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin;
if (target[AclSym] && Object.keys(target[AclSym]).length) {
- if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclEdit;
+ if (target.__fields?.author === Doc.CurrentUserEmail || target.author === Doc.CurrentUserEmail || currentUserGroups.includes("admin")) return AclAdmin;
if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit;
@@ -142,7 +155,8 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
[AclPrivate, 0],
[AclReadonly, 1],
[AclAddonly, 2],
- [AclEdit, 3]
+ [AclEdit, 3],
+ [AclAdmin, 4]
]);
for (const [key, value] of Object.entries(target[AclSym])) {
@@ -156,7 +170,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number)
}
return aclPresent ? effectiveAcl : AclEdit;
}
- return AclEdit;
+ return AclAdmin;
}
export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) {
@@ -165,7 +179,8 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
["Not Shared", 0],
["Can View", 1],
["Can Add", 2],
- ["Can Edit", 3]
+ ["Can Edit", 3],
+ ["Admin", 4]
]);
const dataDoc = target[DataSym];
@@ -205,11 +220,11 @@ const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHe
"chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
let prop = in_prop;
- if (GetEffectiveAcl(target, in_prop) !== AclEdit) {
- return true;
- }
+ const effectiveAcl = GetEffectiveAcl(target, in_prop);
+ if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true;
- if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
+ if (typeof prop === "string" && prop.startsWith("ACL") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
+ // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true;
if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) {
if (!prop.startsWith("_")) {
@@ -229,8 +244,10 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
export function getter(target: any, in_prop: string | symbol | number, receiver: any): any {
let prop = in_prop;
+
+ if (in_prop === FieldsSym || in_prop === Id || in_prop === HandleUpdate || in_prop === CachedUpdates) return target.__fields[prop] || target[prop];
if (in_prop === AclSym) return _overrideAcl ? undefined : target[AclSym];
- if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return undefined;
+ if (GetEffectiveAcl(target) === AclPrivate && !_overrideAcl) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
if (prop === LayoutSym) {
return target.__LAYOUT__;
}