aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgeireann <60007097+geireann@users.noreply.github.com>2020-07-17 14:45:31 +0800
committergeireann <60007097+geireann@users.noreply.github.com>2020-07-17 14:45:31 +0800
commitfc136d87a5c837c9863ca892c0595d3b35a32d18 (patch)
treeb1d5c0ef8d2daee6bf35365827c19c2212401d12 /src
parentae47181bab9bdaa104dc417c4493bc1a0e52eea1 (diff)
parent60819bced2eb67282b77e25c77939dd45d01e7d8 (diff)
Merge branch 'master' into presentation_updates
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/HypothesisAuthenticationManager.tsx2
-rw-r--r--src/client/documents/Documents.ts30
-rw-r--r--src/client/util/CurrentUserUtils.ts61
-rw-r--r--src/client/util/DocumentManager.ts4
-rw-r--r--src/client/util/DragManager.ts50
-rw-r--r--src/client/util/InteractionUtils.tsx15
-rw-r--r--src/client/util/LinkManager.ts13
-rw-r--r--src/client/util/SelectionManager.ts1
-rw-r--r--src/client/util/SettingsManager.scss2
-rw-r--r--src/client/util/SettingsManager.tsx14
-rw-r--r--src/client/views/AntimodeMenu.tsx24
-rw-r--r--src/client/views/ContextMenuItem.tsx6
-rw-r--r--src/client/views/DocumentButtonBar.tsx181
-rw-r--r--src/client/views/DocumentDecorations.tsx55
-rw-r--r--src/client/views/GestureOverlay.scss2
-rw-r--r--src/client/views/GestureOverlay.tsx117
-rw-r--r--src/client/views/InkingStroke.tsx34
-rw-r--r--src/client/views/MainView.scss4
-rw-r--r--src/client/views/MainView.tsx36
-rw-r--r--src/client/views/MetadataEntryMenu.tsx96
-rw-r--r--src/client/views/OverlayView.scss2
-rw-r--r--src/client/views/OverlayView.tsx4
-rw-r--r--src/client/views/TemplateMenu.tsx7
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx4
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx23
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx21
-rw-r--r--src/client/views/collections/CollectionDockingView.scss18
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx147
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx43
-rw-r--r--src/client/views/collections/CollectionMenu.scss (renamed from src/client/views/collections/CollectionViewChromes.scss)52
-rw-r--r--src/client/views/collections/CollectionMenu.tsx (renamed from src/client/views/collections/CollectionViewChromes.tsx)226
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx6
-rw-r--r--src/client/views/collections/CollectionSubView.tsx21
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx31
-rw-r--r--src/client/views/collections/CollectionView.tsx40
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx72
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx90
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.scss64
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx307
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss7
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx326
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss3
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx60
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx5
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx4
-rw-r--r--src/client/views/linking/LinkEditor.scss112
-rw-r--r--src/client/views/linking/LinkEditor.tsx98
-rw-r--r--src/client/views/linking/LinkMenu.scss78
-rw-r--r--src/client/views/linking/LinkMenu.tsx32
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx6
-rw-r--r--src/client/views/linking/LinkMenuItem.scss88
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx91
-rw-r--r--src/client/views/nodes/AudioBox.scss1
-rw-r--r--src/client/views/nodes/AudioBox.tsx1
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx6
-rw-r--r--src/client/views/nodes/ColorBox.tsx12
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx2
-rw-r--r--src/client/views/nodes/DocumentLinksButton.scss17
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx206
-rw-r--r--src/client/views/nodes/DocumentView.tsx145
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/FontIconBox.scss1
-rw-r--r--src/client/views/nodes/FontIconBox.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.tsx48
-rw-r--r--src/client/views/nodes/LabelBox.tsx4
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.scss8
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx14
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx26
-rw-r--r--src/client/views/nodes/PDFBox.tsx2
-rw-r--r--src/client/views/nodes/SliderBox.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx69
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.scss126
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx113
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts3
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx184
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts2
-rw-r--r--src/client/views/pdf/PDFViewer.tsx6
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx6
-rw-r--r--src/fields/documentSchemas.ts9
-rw-r--r--src/mobile/ImageUpload.tsx2
-rw-r--r--src/server/websocket.ts9
83 files changed, 2346 insertions, 1520 deletions
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx
index cffb87227..a7fcf86a4 100644
--- a/src/client/apis/HypothesisAuthenticationManager.tsx
+++ b/src/client/apis/HypothesisAuthenticationManager.tsx
@@ -34,7 +34,7 @@ export default class HypothesisAuthenticationManager extends React.Component<{}>
}
public fetchAccessToken = async (displayIfFound = false) => {
- let response: any = await Networking.FetchFromServer("/readHypothesisAccessToken");
+ const response: any = await Networking.FetchFromServer("/readHypothesisAccessToken");
// if this is an authentication url, activate the UI to register the new access token
if (!response) { // new RegExp(AuthenticationUrl).test(response)) {
this.isOpen = true;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8d9c068dc..080369afe 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -102,6 +102,8 @@ export interface DocumentOptions {
childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
childLayoutString?: string; // template string for collection to use to render its children
hideFilterView?: boolean; // whether to hide the filter popout on collections
+ hideLinkButton?: boolean; // whether the blue link counter button should be hidden
+ hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden
_columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden
isTemplateForField?: string; // the field key for which the containing document is a rendering template
isTemplateDoc?: boolean;
@@ -123,7 +125,7 @@ export interface DocumentOptions {
isBackground?: boolean;
isLinkButton?: boolean;
_columnWidth?: number;
- _fontSize?: number;
+ _fontSize?: string;
_fontFamily?: string;
curPage?: number;
currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds
@@ -138,6 +140,8 @@ export interface DocumentOptions {
dontRegisterChildViews?: boolean;
lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document. see PresBox.
"onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form
+ "onChildDoubleClick-rawScript"?: string; // onChildDoubleClick script in raw text form
+ "onChildClick-rawScript"?: string; // on ChildClick script in raw text form
"onClick-rawScript"?: string; // onClick script in raw text form
"onCheckedClick-rawScript"?: string; // onChecked script in raw text form
"onCheckedClick-params"?: List<string>; // parameter list for onChecked treeview functions
@@ -673,12 +677,12 @@ export namespace Docs {
I.type = DocumentType.INK;
I.layout = InkingStroke.LayoutString("data");
I.color = color;
- I.strokeWidth = strokeWidth;
- I.strokeBezier = strokeBezier;
I.fillColor = fillColor;
- I.arrowStart = arrowStart;
- I.arrowEnd = arrowEnd;
- I.dash = dash;
+ I.strokeWidth = Number(strokeWidth);
+ I.strokeBezier = strokeBezier;
+ I.strokeStartMarker = arrowStart;
+ I.strokeEndMarker = arrowEnd;
+ I.strokeDash = dash;
I.tool = tool;
I.title = "ink";
I.x = options.x;
@@ -687,14 +691,9 @@ export namespace Docs {
I._width = options._width;
I._height = options._height;
I.author = Doc.CurrentUserEmail;
+ I.rotation = 0;
I.data = new InkField(points);
return I;
- // return I;
- // const doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options);
- // doc.color = color;
- // doc.strokeWidth = strokeWidth;
- // doc.tool = tool;
- // return doc;
}
export function PdfDocument(url: string, options: DocumentOptions = {}) {
@@ -784,7 +783,7 @@ export namespace Docs {
export function FontIconDocument(options?: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
+ return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { hideLinkButton: true, ...(options || {}) });
}
export function PresElementBoxDocument(options?: DocumentOptions) {
@@ -908,7 +907,7 @@ export namespace DocUtils {
export let ActiveRecordings: Doc[] = [];
export function MakeLinkToActiveAudio(doc: Doc) {
- DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "", "audio timeline"));
+ DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
}
export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string) {
@@ -917,6 +916,8 @@ export namespace DocUtils {
if (target.doc === Doc.UserDoc()) return undefined;
const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship, layoutKey: "layout_linkView", description }, id);
+ linkDoc.linkDisplay = true;
+ linkDoc.hidden = true;
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');
@@ -980,6 +981,7 @@ export namespace DocUtils {
}
if (type.indexOf("pdf") !== -1) {
ctor = Docs.Create.PdfDocument;
+ if (!options._fitWidth) options._fitWidth = true;
if (!options._width) options._width = 400;
if (!options._height) options._height = options._width * 1200 / 927;
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 9f04aab04..e0cb90b46 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -211,7 +211,7 @@ export class CurrentUserUtils {
details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" "));
const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 };
- const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: 12 };
+ const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12pt" };
const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title" };
const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts });
@@ -402,20 +402,20 @@ export class CurrentUserUtils {
this.setupActiveMobileMenu(doc);
}
return [
- { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
{ title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
{ title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
- { title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
- { title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
- { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
+ { title: "Drag a cat image", label: "Image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
+ { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc },
+ { title: "Drag a screengrabber", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
+ // { title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
{ title: "Drag a audio recorder", label: "Audio", icon: "microphone", ignoreClick: true, drag: `Docs.Create.AudioDocument("${nullAudio}", { _width: 200, title: "ready to record audio" })` },
- { title: "Drag a clickable button", label: "Btn", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
+ { title: "Drag a button", label: "Button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding:10, _yPadding: 10, title: "Button" })' },
{ title: "Drag a presentation view", label: "Prezi", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory,true)`, dragFactory: doc.emptyPresentation as Doc },
{ title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' },
{ title: "Drag a scripting box", label: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc },
- { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
+ // { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' },
{ title: "Drag a mobile view", label: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc },
- { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
+ // { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' },
// { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
// { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc },
@@ -423,8 +423,6 @@ export class CurrentUserUtils {
// { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc },
{ title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
{ title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
- { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
- { title: "Connect a Hypothesis Account", label: "Hypothesis Account", icon: "houzz", click: 'HypothesisAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
];
}
@@ -525,13 +523,13 @@ export class CurrentUserUtils {
// Sets up the title of the button
static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
...opts,
- dropAction: undefined, title: buttonTitle, _fontSize: 37, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)"
+ dropAction: undefined, title: buttonTitle, _fontSize: "37pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)"
}) as any as Doc
// Sets up the description of the button
static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
...opts,
- dropAction: undefined, title: "info", _fontSize: 25, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2,
+ dropAction: undefined, title: "info", _fontSize: "25pt", _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2,
}) as any as Doc
@@ -598,7 +596,7 @@ export class CurrentUserUtils {
_width: 500, lockedPosition: true, _chromeStatus: "disabled", title: "tools stack", forceActive: true
})) as any as Doc;
doc["tabs-button-tools"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 35, _height: 25, title: "Tools", _fontSize: 10,
+ _width: 35, _height: 25, title: "Tools", _fontSize: "10pt",
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
sourcePanel: toolsStack,
onDragStart: ScriptField.MakeFunction('getAlias(this.dragFactory, true)'),
@@ -663,7 +661,7 @@ export class CurrentUserUtils {
lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same"
})) as any as Doc;
doc["tabs-button-library"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 50, _height: 25, title: "Library", _fontSize: 10, targetDropAction: "same",
+ _width: 50, _height: 25, title: "Library", _fontSize: "10pt", targetDropAction: "same",
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
sourcePanel: libraryStack,
onDragStart: ScriptField.MakeFunction('getAlias(this.dragFactory, true)'),
@@ -681,7 +679,7 @@ export class CurrentUserUtils {
static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) {
if (doc["tabs-button-search"] === undefined) {
doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({
- _width: 50, _height: 25, title: "Search", _fontSize: 10,
+ _width: 50, _height: 25, title: "Search", _fontSize: "10pt",
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
sourcePanel: new PrefetchProxy(Docs.Create.QueryDocument({ title: "search stack", })) as any as Doc,
searchFileTypes: new List<string>([DocumentType.RTF, DocumentType.IMG, DocumentType.PDF, DocumentType.VID, DocumentType.WEB, DocumentType.SCRIPTING]),
@@ -777,17 +775,17 @@ export class CurrentUserUtils {
static setupClickEditorTemplates(doc: Doc) {
if (doc["clickFuncs-child"] === undefined) {
+ // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target
const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
- "docCast(thisContainer.target).then((target) => {" +
- " target && docCast(this.source).then((source) => { " +
- " target.proto.data = new List([source || this]); " +
- " }); " +
- "})",
- { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" });
+ "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ",
+ { thisContainer: Doc.name }), {
+ title: "Click to open in target", _width: 300, _height: 200,
+ targetScriptKey: "onChildClick",
+ });
const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
"openOnRight(self.doubleClickView)",
- { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" });
+ {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" });
doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" });
}
@@ -799,14 +797,22 @@ export class CurrentUserUtils {
title: "onClick", "onClick-rawScript": "console.log('click')",
isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200
}, "onClick");
+ const onChildClick = Docs.Create.ScriptingDocument(undefined, {
+ title: "onChildClick", "onChildClick-rawScript": "console.log('child click')",
+ isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200
+ }, "onChildClick");
const onDoubleClick = Docs.Create.ScriptingDocument(undefined, {
title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')",
isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200
}, "onDoubleClick");
+ const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, {
+ title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')",
+ isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200
+ }, "onChildDoubleClick");
const onCheckedClick = Docs.Create.ScriptingDocument(undefined, {
title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, isTemplateForField: "onCheckedClick", _width: 300, _height: 200
}, "onCheckedClick");
- doc.clickFuncs = Docs.Create.TreeDocument([onClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" });
+ doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" });
}
PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
@@ -820,11 +826,12 @@ export class CurrentUserUtils {
doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)");
doc.activeInkWidth = StrCast(doc.activeInkWidth, "1");
doc.activeInkBezier = StrCast(doc.activeInkBezier, "0");
- doc.activeFillColor = StrCast(doc.activeFillColor, "none");
- doc.activeArrowStart = StrCast(doc.activeArrowStart, "none");
- doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "none");
+ doc.activeFillColor = StrCast(doc.activeFillColor, "");
+ doc.activeArrowStart = StrCast(doc.activeArrowStart, "");
+ doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "");
doc.activeDash = StrCast(doc.activeDash, "0");
- doc.fontSize = NumCast(doc.fontSize, 12);
+ doc.fontSize = StrCast(doc.fontSize, "12pt");
+ doc.fontFamily = StrCast(doc.fontFamily, "Arial");
doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e43c3acb9..10b9ad89e 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -146,7 +146,7 @@ export class DocumentManager {
};
const docView = getFirstDocView(targetDoc, originatingDoc);
let annotatedDoc = await Cast(targetDoc.annotationOn, Doc);
- if (annotatedDoc && !linkDoc?.isPushpin) {
+ if (annotatedDoc && !targetDoc?.isPushpin) {
const first = getFirstDocView(annotatedDoc);
if (first) {
annotatedDoc = first.props.Document;
@@ -156,7 +156,7 @@ export class DocumentManager {
}
}
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 (linkDoc?.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);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 2ceafff30..5f34509a1 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -323,7 +323,7 @@ export namespace DragManager {
dragLabel = document.createElement("div");
dragLabel.className = "dragManager-dragLabel";
dragLabel.style.zIndex = "100001";
- dragLabel.style.fontSize = "10";
+ dragLabel.style.fontSize = "10pt";
dragLabel.style.position = "absolute";
// dragLabel.innerText = "press 'a' to embed on drop"; // bcz: need to move this to a status bar
dragDiv.appendChild(dragLabel);
@@ -427,6 +427,54 @@ export namespace DragManager {
}, dragData.droppedDocuments);
}
+ const target = document.elementFromPoint(e.x, e.y);
+
+ const complete = new DragCompleteEvent(false, dragData);
+
+ if (target) {
+ target.dispatchEvent(
+ new CustomEvent<React.DragEvent>("dashDragging", {
+ bubbles: true,
+ detail: {
+ shiftKey: e.shiftKey,
+ altKey: e.altKey,
+ metaKey: e.metaKey,
+ ctrlKey: e.ctrlKey,
+ clientX: e.clientX,
+ clientY: e.clientY,
+ dataTransfer: new DataTransfer,
+ button: e.button,
+ buttons: e.buttons,
+ getModifierState: e.getModifierState,
+ movementX: e.movementX,
+ movementY: e.movementY,
+ pageX: e.pageX,
+ pageY: e.pageY,
+ relatedTarget: e.relatedTarget,
+ screenX: e.screenX,
+ screenY: e.screenY,
+ detail: e.detail,
+ view: e.view ? e.view : new Window,
+ nativeEvent: new DragEvent("dashDragging"),
+ currentTarget: target,
+ target: target,
+ bubbles: true,
+ cancelable: true,
+ defaultPrevented: true,
+ eventPhase: e.eventPhase,
+ isTrusted: true,
+ preventDefault: e.preventDefault,
+ isDefaultPrevented: () => true,
+ stopPropagation: e.stopPropagation,
+ isPropagationStopped: () => true,
+ persist: emptyFunction,
+ timeStamp: e.timeStamp,
+ type: "dashDragging"
+ }
+ })
+ );
+ }
+
const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
const moveX = thisX - lastX;
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 02b444cd3..07adbb8b1 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -94,6 +94,7 @@ export namespace InteractionUtils {
export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number,
color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string,
dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) {
+
let pts: { X: number; Y: number; }[] = [];
if (shape) { //if any of the shape are true
pts = makePolygon(shape, points);
@@ -119,15 +120,15 @@ export namespace InteractionUtils {
const dashArray = String(Number(width) * Number(dash));
const defGuid = Utils.GenerateGuid();
const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth)));
- return (<svg fill={fill === "none" ? color : fill}> {/* setting the svg fill sets the arrowhead fill */}
+ return (<svg fill={color}> {/* setting the svg fill sets the arrowStart fill */}
{nodefs ? (null) : <defs>
{arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : <marker id={`dot${defGuid}`} orient="auto" overflow="visible">
<circle r={1} fill="context-stroke" />
</marker>}
- {arrowStart !== "arrowHead" && arrowEnd !== "arrowHead" ? (null) : <marker id={`arrowHead${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
+ {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : <marker id={`arrowStart${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
<polygon points={`${arrowDim} ${-Math.max(1, arrowDim / 2)}, ${arrowDim} ${Math.max(1, arrowDim / 2)}, -1 0`} />
</marker>}
- {arrowStart !== "arrowEnd" && arrowEnd !== "arrowEnd" ? (null) : <marker id={`arrowEnd${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
+ {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : <marker id={`arrowEnd${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7">
<polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} />
</marker>}
</defs>}
@@ -136,7 +137,7 @@ export namespace InteractionUtils {
points={strpts}
style={{
filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
- fill,
+ fill: fill ? fill : "transparent",
opacity: strokeWidth !== width ? 0.5 : undefined,
pointerEvents: pevents as any,
stroke: color ?? "rgb(0, 0, 0)",
@@ -145,8 +146,8 @@ export namespace InteractionUtils {
strokeLinecap: "round",
strokeDasharray: dashArray
}}
- markerStart={`url(#${arrowStart + defGuid})`}
- markerEnd={`url(#${arrowEnd + defGuid})`}
+ markerStart={`url(#${arrowStart + "Start" + defGuid})`}
+ markerEnd={`url(#${arrowEnd + "End" + defGuid})`}
/>
</svg>);
@@ -155,7 +156,7 @@ export namespace InteractionUtils {
// export function makeArrow() {
// return (
// InkOptionsMenu.Instance.getColors().map(color => {
- // const id1 = "arrowHeadTest" + color;
+ // const id1 = "arrowStartTest" + color;
// console.log(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} />
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 6da581f35..974744344 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -3,6 +3,7 @@ import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast, StrCast } from "../../fields/Types";
import { Scripting } from "./Scripting";
+import { undoBatch } from "./UndoManager";
/*
* link doc:
@@ -52,6 +53,7 @@ export class LinkManager {
return false;
}
+ @undoBatch
public deleteLink(linkDoc: Doc): boolean {
if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) {
Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc);
@@ -61,12 +63,17 @@ export class LinkManager {
}
// finds all links that contain the given anchor
- public getAllRelatedLinks(anchor: Doc): Doc[] {
+ public getAllDirectLinks(anchor: Doc): Doc[] {
const related = LinkManager.Instance.getAllLinks().filter(link => {
const protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null));
const protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null));
return protomatch1 || protomatch2 || Doc.AreProtosEqual(link, anchor);
});
+ return related;
+ }
+ // finds all links that contain the given anchor
+ public getAllRelatedLinks(anchor: Doc): Doc[] {
+ const related = LinkManager.Instance.getAllDirectLinks(anchor);
DocListCast(anchor[Doc.LayoutFieldKey(anchor) + "-annotations"]).map(anno => {
related.push(...LinkManager.Instance.getAllRelatedLinks(anno));
});
@@ -206,4 +213,6 @@ export class LinkManager {
}
Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); },
- "creates a link to inputted document", "(doc: any)"); \ No newline at end of file
+ "returns all the links to the document or its annotations", "(doc: any)");
+Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); },
+ "returns all the links directly to the document", "(doc: any)"); \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 024532f90..9a968aeda 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -14,6 +14,7 @@ export namespace SelectionManager {
SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
@action
SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
+
// if doc is not in SelectedDocuments, add it
if (!manager.SelectedDocuments.get(docView)) {
if (!ctrlPressed) {
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index 13c65042c..6d394a38d 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -56,7 +56,7 @@
.settings-type {
display: flex;
flex-direction: column;
- flex-basis: 30%;
+ flex-basis: 45%;
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 9888cce48..981ee698d 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -11,6 +11,8 @@ import { Networking } from "../Network";
import { CurrentUserUtils } from "./CurrentUserUtils";
import { Utils } from "../../Utils";
import { Doc } from "../../fields/Doc";
+import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager";
+import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager";
library.add(fa.faWindowClose);
@@ -83,6 +85,14 @@ export default class SettingsManager extends React.Component<{}> {
noviceToggle = (event: any) => {
Doc.UserDoc().noviceMode = !Doc.UserDoc().noviceMode;
}
+ @action
+ googleAuthorize = (event: any) => {
+ GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true);
+ }
+ @action
+ hypothesisAuthorize = (event: any) => {
+ HypothesisAuthenticationManager.Instance.fetchAccessToken(true);
+ }
private get settingsInterface() {
return (
@@ -96,7 +106,9 @@ export default class SettingsManager extends React.Component<{}> {
<div className="settings-body">
<div className="settings-type">
<button onClick={this.onClick} value="password">reset password</button>
- <button onClick={this.noviceToggle} value="data">{`toggle ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
+ <button onClick={this.noviceToggle} value="data">{`Set ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button>
+ <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button>
+ <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button>
<button onClick={() => window.location.assign(Utils.prepend("/logout"))}>
{CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"}
</button>
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
index cb293dee4..68ccefcb5 100644
--- a/src/client/views/AntimodeMenu.tsx
+++ b/src/client/views/AntimodeMenu.tsx
@@ -134,13 +134,35 @@ export default abstract class AntimodeMenu extends React.Component {
protected getElement(buttons: JSX.Element[]) {
return (
<div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
- style={{ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay }}>
+ style={{
+ left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay,
+ position: this.Pinned ? "unset" : undefined
+ }}>
<div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />
{buttons}
</div>
);
}
+ protected getElementVert(buttons: JSX.Element[]) {
+ return (
+ <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
+ style={{
+ left: this.Pinned ? undefined : this._left,
+ top: this.Pinned ? 0 : this._top,
+ right: this.Pinned ? 0 : undefined,
+ height: "inherit",
+ width: 200,
+ opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay,
+ position: this.Pinned ? "absolute" : undefined
+ }}>
+ {buttons}
+ </div>
+ );
+ }
+
+
+
protected getElementWithRows(rows: JSX.Element[], numRows: number, hasDragger: boolean = true) {
return (
<div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 99840047f..68ebd8e14 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -19,6 +19,7 @@ export interface OriginalMenuProps {
export interface SubmenuProps {
description: string;
subitems: ContextMenuProps[];
+ noexpand?: boolean;
icon: IconProp; //maybe should be optional (icon?)
closeMenu?: () => void;
}
@@ -96,6 +97,11 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
<div className="contextMenu-subMenu-cont" style={{ marginLeft: "25%", left: "0px", marginTop }}>
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
</div>;
+ if (!("noexpand" in this.props)) {
+ return <>
+ {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
+ </>;
+ }
return (
<div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} style={{ alignItems: where }}
onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 1667b2f65..45f4c7393 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -11,7 +11,6 @@ import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager';
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
import { Docs, DocUtils } from '../documents/Documents';
import { DragManager } from '../util/DragManager';
-import { UndoManager } from "../util/UndoManager";
import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView';
import { ParentDocSelector } from './collections/ParentDocumentSelector';
import './collections/ParentDocumentSelector.scss';
@@ -23,6 +22,7 @@ import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
+import { Tooltip } from '@material-ui/core';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -118,18 +118,18 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const targetDoc = this.view0?.props.Document;
const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none";
- return !targetDoc ? (null) : <div
- title={`${published ? "Push" : "Publish"} to Google Docs`}
- className="documentButtonBar-linker"
- style={{ animation }}
- onClick={async () => {
- await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
- !published && runInAction(() => this.isAnimatingPulse = true);
- DocumentButtonBar.hasPushedHack = false;
- targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} />
- </div>;
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}>
+ <div
+ className="documentButtonBar-linker"
+ style={{ animation }}
+ onClick={async () => {
+ await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken();
+ !published && runInAction(() => this.isAnimatingPulse = true);
+ DocumentButtonBar.hasPushedHack = false;
+ targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} />
+ </div></Tooltip>;
}
@computed
@@ -137,82 +137,87 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
const targetDoc = this.view0?.props.Document;
const dataDoc = targetDoc && Doc.GetProto(targetDoc);
const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
- return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <div className="documentButtonBar-linker"
- title={(() => {
- switch (this.openHover) {
- default:
- case UtilityButtonState.Default: return `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
- case UtilityButtonState.OpenRight: return "Open in Right Split";
- case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
- }
- })()}
- style={{ backgroundColor: this.pullColor }}
- onPointerEnter={action(e => {
- if (e.altKey) {
- this.openHover = UtilityButtonState.OpenExternally;
- } else if (e.shiftKey) {
- this.openHover = UtilityButtonState.OpenRight;
- }
- })}
- onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
- onClick={async e => {
- const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
- if (e.shiftKey) {
- e.preventDefault();
- let googleDoc = await Cast(dataDoc.googleDoc, Doc);
- if (!googleDoc) {
- const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
- googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
- dataDoc.googleDoc = googleDoc;
+
+ const title = (() => {
+ switch (this.openHover) {
+ default:
+ case UtilityButtonState.Default: return `${!dataDoc?.unchanged ? "Pull from" : "Fetch"} Google Docs`;
+ case UtilityButtonState.OpenRight: return "Open in Right Split";
+ case UtilityButtonState.OpenExternally: return "Open in new Browser Tab";
+ }
+ })();
+
+ return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip
+ title={<><div className="dash-tooltip">{title}</div></>}>
+ <div className="documentButtonBar-linker"
+ style={{ backgroundColor: this.pullColor }}
+ onPointerEnter={action(e => {
+ if (e.altKey) {
+ this.openHover = UtilityButtonState.OpenExternally;
+ } else if (e.shiftKey) {
+ this.openHover = UtilityButtonState.OpenRight;
}
- CollectionDockingView.AddRightSplit(googleDoc);
- } else if (e.altKey) {
- e.preventDefault();
- window.open(googleDocUrl);
- } else {
- this.clearPullColor();
- DocumentButtonBar.hasPulledHack = false;
- targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
- dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
- }
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm"
- style={{ WebkitAnimation: animation, MozAnimation: animation }}
- icon={(() => {
- switch (this.openHover) {
- default:
- case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
- case UtilityButtonState.OpenRight: return "arrow-alt-circle-right";
- case UtilityButtonState.OpenExternally: return "share";
+ })}
+ onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)}
+ onClick={async e => {
+ const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`;
+ if (e.shiftKey) {
+ e.preventDefault();
+ let googleDoc = await Cast(dataDoc.googleDoc, Doc);
+ if (!googleDoc) {
+ const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false };
+ googleDoc = Docs.Create.WebDocument(googleDocUrl, options);
+ dataDoc.googleDoc = googleDoc;
+ }
+ CollectionDockingView.AddRightSplit(googleDoc);
+ } else if (e.altKey) {
+ e.preventDefault();
+ window.open(googleDocUrl);
+ } else {
+ this.clearPullColor();
+ DocumentButtonBar.hasPulledHack = false;
+ targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
+ dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
}
- })()}
- />
- </div>;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm"
+ style={{ WebkitAnimation: animation, MozAnimation: animation }}
+ icon={(() => {
+ switch (this.openHover) {
+ default:
+ case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
+ case UtilityButtonState.OpenRight: return "arrow-alt-circle-right";
+ case UtilityButtonState.OpenExternally: return "share";
+ }
+ })()}
+ />
+ </div></Tooltip>;
}
@computed
get pinButton() {
const targetDoc = this.view0?.props.Document;
const isPinned = targetDoc && Doc.isDocPinned(targetDoc);
- return !targetDoc ? (null) : <div className="documentButtonBar-linker"
- title={Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}
- style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
- onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
- <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
- />
- </div>;
+ return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}>
+ <div className="documentButtonBar-linker"
+ style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
+ onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
+ />
+ </div></Tooltip>;
}
@computed
get metadataButton() {
const view0 = this.view0;
- return !view0 ? (null) : <div title="Show metadata panel" className="documentButtonBar-linkFlyout">
- <Flyout anchorPoint={anchorPoints.LEFT_TOP}
- content={<MetadataEntryMenu docs={() => this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
- <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
- </div>
- </Flyout>
- </div>;
+ return !view0 ? (null) : <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>}>
+ <div className="documentButtonBar-linkFlyout">
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={<MetadataEntryMenu docs={this.props.views().filter(dv => dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}>
+ <div className={"documentButtonBar-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} >
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />}
+ </div>
+ </Flyout>
+ </div></Tooltip>;
}
@computed
@@ -253,14 +258,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
Array.from(Object.values(Templates.TemplateList)).map(template =>
templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean)));
return !view0 ? (null) :
- <div title="Tap: Customize layout. Drag: Create alias" className="documentButtonBar-linkFlyout" ref={this._dragRef}>
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
- content={!this._aliasDown ? (null) : <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
- <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
- {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
- </div>
- </Flyout>
- </div>;
+ <Tooltip title={<><div className="dash-tooltip">Tap: Customize layout. Drag: Create alias</div></>}>
+ <div className="documentButtonBar-linkFlyout" ref={this._dragRef}>
+ <Flyout anchorPoint={anchorPoints.LEFT_TOP} onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)}
+ content={!this._aliasDown ? (null) : <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}>
+ <div className={"documentButtonBar-linkButton-empty"} ref={this._dragRef} onPointerDown={this.onAliasButtonDown} >
+ {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />}
+ </div>
+ </Flyout>
+ </div></Tooltip>;
}
render() {
@@ -271,8 +277,11 @@ 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} />
+ <DocumentLinksButton 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} />
+ </div> : null}
<div className="documentButtonBar-button">
{this.templateButton}
</div>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a45ef8862..35c040f86 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -17,14 +17,12 @@ import { DocumentButtonBar } from './DocumentButtonBar';
import './DocumentDecorations.scss';
import { DocumentView } from "./nodes/DocumentView";
import React = require("react");
-import { Id, Copy, Update } from '../../fields/FieldSymbols';
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 { update } from 'serializr';
-import { Transform } from "../util/Transform";
+import { Tooltip } from '@material-ui/core';
library.add(faCaretUp);
library.add(faObjectGroup);
@@ -83,6 +81,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.renderDepth === 0 ||
documentView.props.treeViewDoc ||
+ !documentView.ContentDiv ||
Doc.AreProtosEqual(documentView.props.Document, Doc.UserDoc())) {
return bounds;
}
@@ -90,7 +89,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
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) {
- const docuBox = documentView.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
+ const docuBox = documentView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
if (docuBox.length) {
const rect = docuBox[0].getBoundingClientRect();
sptX = rect.left;
@@ -152,8 +151,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (e.button === 0 && !e.altKey && !e.ctrlKey) {
let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0];
while (child.children.length) {
- const next = Array.from(child.children).find(c => !c.className.includes("collectionViewChrome"));
- if (next?.className.includes("documentView-node")) break;
+ const next = Array.from(child.children).find(c => typeof (c.className) !== "string");
+ if (typeof (next?.className) === "string" && next?.className.includes("documentView-node")) break;
if (next) child = next;
else break;
}
@@ -293,6 +292,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
const doc = Document(element.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) {
@@ -433,6 +433,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let nheight = doc._nativeHeight || 0;
const width = (doc._width || 0);
let height = (doc._height || (nheight / nwidth * width));
+ height = !height || isNaN(height) ? 20 : height;
const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
if (nwidth && nheight) {
if (nwidth / nheight !== width / height) {
@@ -545,13 +546,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
const minimal = bounds.r - bounds.x < 100 ? true : false;
const maximizeIcon = minimal ? (
- <div className="documentDecorations-contextMenu" title="Show context menu" onPointerDown={this.onSettingsDown}>
- <FontAwesomeIcon size="lg" icon="cog" />
- </div>) : (
- <div className="documentDecorations-minimizeButton" title="Iconify" 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 title={<><div className="dash-tooltip">Show context menu</div></>} placement="top">
+ <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}>
+ {/* 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>);
const titleArea = this._edtingTitle ?
<>
@@ -571,11 +574,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>}
</> :
<>
- {minimal ? (null) : <div className="documentDecorations-contextMenu" key="menu" title="Show context menu" onPointerDown={this.onSettingsDown}>
+ {minimal ? (null) : <Tooltip title={<><div className="dash-tooltip">Show context menu</div></>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}>
<FontAwesomeIcon size="lg" icon="cog" />
- </div>}
+ </div></Tooltip>}
<div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
- <span style={{ width: "100%", display: "inline-block" }}>{`${this.selectionTitle}`}</span>
+ <span style={{ width: "100%", display: "inline-block", cursor: "move" }}>{`${this.selectionTitle}`}</span>
</div>
</>;
@@ -610,12 +613,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
{maximizeIcon}
{titleArea}
{SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
- <div className="documentDecorations-iconifyButton" title={`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`} onPointerDown={this.onIconifyDown}>
- {"_"}
- </div>}
- <div className="documentDecorations-closeButton" title="Open Document in Tab" onPointerDown={this.onMaximizeDown}>
+ <Tooltip title={<><div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div></>} placement="top">
+ <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}>
{SelectionManager.SelectedDocuments().length === 1 ? DocumentDecorations.DocumentIcon(StrCast(seldoc.props.Document.layout, "...")) : "..."}
- </div>
+ </div></Tooltip>
<div id="documentDecorations-rotation" className="documentDecorations-rotation"
onPointerDown={this.onRotateDown}> ⟲ </div>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer"
@@ -636,10 +640,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
{seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) :
- <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
- title="tap to select containing document" onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
- <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
- </div>}
+ <Tooltip title={<><div className="dash-tooltip">tap to select containing document</div></>} placement="top">
+ <div id="documentDecorations-levelSelector" className="documentDecorations-selector"
+ onPointerDown={this.onSelectorUp} onContextMenu={e => e.preventDefault()}>
+ <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
+ </div></Tooltip>}
<div id="documentDecorations-borderRadius" className="documentDecorations-radius"
onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index f61f4a05e..c9d78890e 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -1,7 +1,7 @@
.gestureOverlay-cont {
width: 100vw;
height: 100vh;
- position: fixed;
+ position: relative;
top: 0;
left: 0;
touch-action: none;
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index cdc468066..2f34cbc05 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -7,9 +7,8 @@ import { Cast, FieldValue, NumCast } from "../../fields/Types";
import MobileInkOverlay from "../../mobile/MobileInkOverlay";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { MobileInkOverlayContent } from "../../server/Message";
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from "../../Utils";
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter, setupMoveUpEvents } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { DocServer } from "../DocServer";
import { DocUtils } from "../documents/Documents";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
@@ -23,7 +22,7 @@ import { RadialMenu } from "./nodes/RadialMenu";
import HorizontalPalette from "./Palette";
import { Touchable } from "./Touchable";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
-import HeightLabel from "./collections/collectionMulticolumn/MultirowHeightLabel";
+import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu";
@observer
export default class GestureOverlay extends Touchable {
@@ -55,6 +54,7 @@ export default class GestureOverlay extends Touchable {
@observable private showMobileInkOverlay: boolean = false;
+ private _overlayRef = React.createRef<HTMLDivElement>();
private _d1: Doc | undefined;
private _inkToTextDoc: Doc | undefined;
private _thumbDoc: Doc | undefined;
@@ -497,39 +497,26 @@ export default class GestureOverlay extends Touchable {
onPointerDown = (e: React.PointerEvent) => {
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
this._points.push({ X: e.clientX, Y: e.clientY });
- e.stopPropagation();
- e.preventDefault();
-
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
+ setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
}
}
@action
onPointerMove = (e: PointerEvent) => {
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) {
- this._points.push({ X: e.clientX, Y: e.clientY });
- e.stopPropagation();
- e.preventDefault();
-
-
- if (this._points.length > 1) {
- const B = this.svgBounds;
- const initialPoint = this._points[0.];
- const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
- const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
- if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
- switch (this.Tool) {
- case ToolglassTools.RadialMenu:
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- //this.handle1PointerHoldStart(e);
- }
+ this._points.push({ X: e.clientX, Y: e.clientY });
+
+ if (this._points.length > 1) {
+ const B = this.svgBounds;
+ const initialPoint = this._points[0.];
+ const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height;
+ const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
+ if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
+ switch (this.Tool) {
+ case ToolglassTools.RadialMenu: return true;
}
}
}
+ return false;
}
handleLineGesture = (): boolean => {
@@ -588,8 +575,6 @@ export default class GestureOverlay extends Touchable {
if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) {
switch (this.Tool) {
case ToolglassTools.InkToText:
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
this._strokes.push(new Array(...this._points));
this._points = [];
CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => {
@@ -623,7 +608,7 @@ export default class GestureOverlay extends Touchable {
this.makePolygon(this.InkShape, false);
this.dispatchGesture(GestureUtils.Gestures.Stroke);
this._points = [];
- if (this.InkShape !== "noRec") {
+ if (!InkOptionsMenu.Instance._keepMode) {
this.InkShape = "";
}
}
@@ -633,54 +618,35 @@ export default class GestureOverlay extends Touchable {
let actionPerformed = false;
if (result && result.Score > 0.7) {
switch (result.Name) {
- case GestureUtils.Gestures.Box:
- this.dispatchGesture(GestureUtils.Gestures.Box);
- actionPerformed = true;
- break;
- case GestureUtils.Gestures.StartBracket:
- this.dispatchGesture(GestureUtils.Gestures.StartBracket);
- actionPerformed = true;
- break;
- case GestureUtils.Gestures.EndBracket:
- this.dispatchGesture("endbracket");
- actionPerformed = true;
- break;
- case GestureUtils.Gestures.Line:
- actionPerformed = this.handleLineGesture();
- break;
- case GestureUtils.Gestures.Triangle:
- this.makePolygon("triangle", true);
- break;
- case GestureUtils.Gestures.Circle:
- this.makePolygon("circle", true);
- break;
- case GestureUtils.Gestures.Rectangle:
- this.makePolygon("rectangle", true);
- break;
- case GestureUtils.Gestures.Scribble:
- console.log("scribble");
- break;
- }
- if (actionPerformed) {
- this._points = [];
+ case GestureUtils.Gestures.Box: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box); break;
+ case GestureUtils.Gestures.StartBracket: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket); break;
+ case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture("endbracket"); break;
+ case GestureUtils.Gestures.Line: actionPerformed = this.handleLineGesture(); break;
+ case GestureUtils.Gestures.Triangle: actionPerformed = this.makePolygon("triangle", true); break;
+ case GestureUtils.Gestures.Circle: actionPerformed = this.makePolygon("circle", true); break;
+ case GestureUtils.Gestures.Rectangle: actionPerformed = this.makePolygon("rectangle", true); break;
+ case GestureUtils.Gestures.Scribble: console.log("scribble"); break;
}
}
// if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document
if (!actionPerformed) {
this.dispatchGesture(GestureUtils.Gestures.Stroke);
- this._points = [];
}
+ this._points = [];
}
} else {
this._points = [];
}
- SetActiveArrowStart("none");
- GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
- SetActiveArrowEnd("none");
- GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
+ //get out of ink mode after each stroke=
+ if (!InkOptionsMenu.Instance._keepMode) {
+ Doc.SetSelectedTool(InkTool.None);
+ InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum;
+ SetActiveArrowStart("none");
+ GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart();
+ SetActiveArrowEnd("none");
+ GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd();
+ }
}
makePolygon = (shape: string, gesture: boolean) => {
@@ -691,7 +657,7 @@ export default class GestureOverlay extends Touchable {
var bottom = Math.max(...ys);
var top = Math.min(...ys);
if (shape === "noRec") {
- return;
+ return false;
}
if (!gesture) {
//if shape options is activated in inkOptionMenu
@@ -772,11 +738,12 @@ export default class GestureOverlay extends Touchable {
this._points.push({ X: x2, Y: y2 });
// this._points.push({ X: x1, Y: y1 - 1 });
}
+ return true;
}
dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => {
const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
- target?.dispatchEvent(
+ return target?.dispatchEvent(
new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
{
bubbles: true,
@@ -788,7 +755,7 @@ export default class GestureOverlay extends Touchable {
}
}
)
- );
+ ) || false;
}
getBounds = (stroke: InkData) => {
@@ -807,10 +774,11 @@ export default class GestureOverlay extends Touchable {
@computed get elements() {
const width = Number(ActiveInkWidth());
+ const rect = this._overlayRef.current?.getBoundingClientRect();
const B = this.svgBounds;
B.left = B.left - width / 2;
B.right = B.right + width / 2;
- B.top = B.top - width / 2;
+ B.top = B.top - width / 2 - (rect?.y || 0);
B.bottom = B.bottom + width / 2;
B.width += width;
B.height += width;
@@ -827,7 +795,7 @@ export default class GestureOverlay extends Touchable {
}),
this._points.length <= 1 ? (null) : <svg key="svg" width={B.width} height={B.height}
style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000, overflow: "visible" }}>
- {InteractionUtils.CreatePolyline(this._points, B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false, false)}
+ {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), 1, 1, this.InkShape, "none", false, false)}
</svg>]
];
}
@@ -876,7 +844,8 @@ export default class GestureOverlay extends Touchable {
render() {
return (
- <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
+ <div className="gestureOverlay-cont" ref={this._overlayRef}
+ onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
{this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
{this.elements}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 7d6e7e5dd..abc698e62 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -15,6 +15,8 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
import { Scripting } from "../util/Scripting";
import { Doc } from "../../fields/Doc";
+import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
+import { action } from "mobx";
library.add(faPaintBrush);
@@ -38,10 +40,16 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
this.props.Document.isInkMask = true;
}
+ @action
+ private formatShape = () => {
+ FormatShapePane.Instance.Pinned = true;
+ }
+
render() {
TraceMobx();
const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
- const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
+ // const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth()));
+ const strokeWidth = Number(this.layoutDoc.strokeWidth);
const xs = data.map(p => p.X);
const ys = data.map(p => p.Y);
const left = Math.min(...xs) - strokeWidth / 2;
@@ -52,14 +60,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const height = bottom - top;
const scaleX = (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth);
const scaleY = (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth);
- const strokeColor = StrCast(this.layoutDoc.color, ActiveInkColor());
+ const strokeColor = StrCast(this.layoutDoc.color, "");
const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth,
- StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()),
- StrCast(this.layoutDoc.arrowStart, ActiveArrowStart()), StrCast(this.layoutDoc.arrowEnd, ActiveArrowEnd()),
- StrCast(this.layoutDoc.dash, ActiveDash()), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false);
+ 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, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()),
+ StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"),
"none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true);
return (
<svg className="inkingStroke"
@@ -72,8 +80,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
overflow: "visible",
}}
onContextMenu={() => {
- ContextMenu.Instance?.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
- ContextMenu.Instance?.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+ const cm = ContextMenu.Instance;
+ if (cm) {
+ cm.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" });
+ cm.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" });
+ cm.addItem({ description: "Format Shape", event: this.formatShape, icon: "paint-brush" });
+ }
}}
><defs>
</defs>
@@ -94,9 +106,9 @@ export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkP
export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); }
export function ActiveInkPen(): Doc { return Cast(Doc.UserDoc().activeInkPen, Doc, null); }
export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); }
-export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, "none"); }
-export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, "none"); }
-export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, "none"); }
+export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); }
+export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); }
+export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); }
export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); }
export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); }
export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); }
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 5b142ffda..e1ddbc533 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,6 +1,10 @@
@import "globalCssVariables";
@import "nodeModuleOverrides";
+.dash-tooltip {
+ font-size: 11px;
+ padding: 2px;
+}
.mainView-tabButtons {
position: relative;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index e67faea1b..e085a9c4a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,4 +1,5 @@
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,
@@ -6,7 +7,8 @@ import {
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,
- faAngleDown, faAngleUp, faSearchPlus, faPlayCircle, faClock, faRocket
+ faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
+ faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faAngleDown, faAngleUp, faSearchPlus, faPlayCircle, faClock, faRocket
} from '@fortawesome/free-solid-svg-icons';
import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -60,10 +62,11 @@ import { DocumentManager } from '../util/DocumentManager';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { LinkMenu } from './linking/LinkMenu';
import { LinkDocPreview } from './nodes/LinkDocPreview';
-import { Fade } from '@material-ui/core';
import { LinkCreatedBox } from './nodes/LinkCreatedBox';
import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
+import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane";
import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager';
+import CollectionMenu from './collections/CollectionMenu';
@observer
export class MainView extends React.Component {
@@ -147,7 +150,8 @@ export class MainView extends React.Component {
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,
- faAngleDown, faAngleUp, faSearchPlus, faPlayCircle, faClock, faRocket);
+ faHeading, faRulerCombined, faFillDrip, faLink, faUnlink, faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper,
+ faPaintRoller, faBars, faBrush, faShapes, faEllipsisH, faHandPaper, faAngleDown, faAngleUp, faSearchPlus, faPlayCircle, faClock, faRocket);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -388,7 +392,7 @@ export class MainView extends React.Component {
doc.dockingConfig ? this.openWorkspace(doc) :
CollectionDockingView.AddRightSplit(doc, libraryPath);
}
- sidebarScreenToLocal = () => new Transform(0, RichTextMenu.Instance.Pinned ? -35 : 0, 1);
+ sidebarScreenToLocal = () => new Transform(0, (RichTextMenu.Instance.Pinned ? -35 : 0) + (InkOptionsMenu.Instance.Pinned ? -35 : 0) + (CollectionMenu.Instance.Pinned ? -35 : 0), 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(0, -this._buttonBarHeight);
@computed get flyout() {
@@ -465,10 +469,14 @@ export class MainView extends React.Component {
@computed get mainContent() {
const sidebar = this.userDoc?.["tabs-panelContainer"];
+ const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (InkOptionsMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0);
+ const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`;
return !this.userDoc || !(sidebar instanceof Doc) ? (null) : (
<div className="mainView-mainContent" style={{
color: this.darkScheme ? "rgb(205,205,205)" : "black",
- height: RichTextMenu.Instance?.Pinned ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%"
+ //change to times 2 for both pinned
+ height,
+ width: (FormatShapePane.Instance?.Pinned) ? `calc(100% - 200px)` : "100%"
}} >
<div style={{ display: "contents", flexDirection: "row", position: "relative" }}>
<div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}>
@@ -607,22 +615,26 @@ export class MainView extends React.Component {
<GoogleAuthenticationManager />
<HypothesisAuthenticationManager />
<DocumentDecorations />
- <GestureOverlay>
- <RichTextMenu key="rich" />
- {this.mainContent}
- </GestureOverlay>
- <PreviewCursor />
- <LinkCreatedBox />
+ <CollectionMenu />
+ <InkOptionsMenu />
+ <FormatShapePane />
+ <RichTextMenu key="rich" />
{LinkDescriptionPopup.descriptionPopup ? <LinkDescriptionPopup /> : null}
{DocumentLinksButton.EditLink ? <LinkMenu location={DocumentLinksButton.EditLinkLoc} docView={DocumentLinksButton.EditLink} addDocTab={DocumentLinksButton.EditLink.props.addDocTab} changeFlyout={emptyFunction} /> : (null)}
{LinkDocPreview.LinkInfo ? <LinkDocPreview location={LinkDocPreview.LinkInfo.Location} backgroundColor={this.defaultBackgroundColors}
linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href}
addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)}
+ <GestureOverlay >
+ {this.mainContent}
+ </GestureOverlay>
+ <PreviewCursor />
+ <LinkCreatedBox />
<ContextMenu />
+ <FormatShapePane />
<RadialMenu />
<PDFMenu />
<MarqueeOptionsMenu />
- <InkOptionsMenu />
+
<OverlayView />
<TimelineMenu />
{this.snapLines}
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index b0752ffb2..ca8a6e1d7 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -3,14 +3,14 @@ import "./MetadataEntryMenu.scss";
import { observer } from 'mobx-react';
import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
import { KeyValueBox } from './nodes/KeyValueBox';
-import { Doc, Field, DocListCastAsync } from '../../fields/Doc';
+import { Doc, Field, DocListCastAsync, DocListCast } from '../../fields/Doc';
import * as Autosuggest from 'react-autosuggest';
-import { undoBatch } from '../util/UndoManager';
+import { undoBatch, UndoManager } from '../util/UndoManager';
import { emptyFunction, emptyPath } from '../../Utils';
export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
export interface MetadataEntryProps {
- docs: DocLike | (() => DocLike);
+ docs: Doc[];
onError?: () => boolean;
suggestWithFunction?: boolean;
}
@@ -39,26 +39,13 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
let onProto: boolean = false;
let value: string | undefined = undefined;
let docs = this.props.docs;
- if (typeof docs === "function") {
- if (this.props.suggestWithFunction) {
- docs = docs();
- } else {
- return;
- }
- }
- docs = await docs;
- if (docs instanceof Doc) {
- await docs[this._currentKey];
- value = Field.toKeyValueString(docs, this._currentKey);
- } else {
- for (const doc of docs) {
- const v = await doc[this._currentKey];
- onProto = onProto || !Object.keys(doc).includes(this._currentKey);
- if (field === null) {
- field = v;
- } else if (v !== field) {
- value = "multiple values";
- }
+ for (const doc of docs) {
+ const v = await doc[this._currentKey];
+ onProto = onProto || !Object.keys(doc).includes(this._currentKey);
+ if (field === null) {
+ field = v;
+ } else if (v !== field) {
+ value = "multiple values";
}
}
if (value === undefined) {
@@ -86,27 +73,16 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
const script = KeyValueBox.CompileKVPScript(this._currentValue);
if (!script) return;
- let doc = this.props.docs;
- if (typeof doc === "function") {
- doc = doc();
- }
- doc = await doc;
-
- let success: boolean;
- if (doc instanceof Doc) {
- success = KeyValueBox.ApplyKVPScript(doc, this._currentKey, script);
- } else {
- let childSuccess = true;
- if (this._addChildren) {
- for (const document of doc) {
- const collectionChildren = await DocListCastAsync(document.data);
- if (collectionChildren) {
- childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script));
- }
+ let childSuccess = true;
+ if (this._addChildren) {
+ for (const document of this.props.docs) {
+ const collectionChildren = DocListCast(document.data);
+ if (collectionChildren) {
+ childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script));
}
}
- success = doc.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess;
}
+ const success = this.props.docs.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess;
if (!success) {
if (this.props.onError) {
if (this.props.onError()) {
@@ -132,24 +108,12 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
}
}
- getKeySuggestions = async (value: string): Promise<string[]> => {
+ getKeySuggestions = (value: string) => {
value = value.toLowerCase();
let docs = this.props.docs;
- if (typeof docs === "function") {
- if (this.props.suggestWithFunction) {
- docs = docs();
- } else {
- return [];
- }
- }
- docs = await docs;
- if (docs instanceof Doc) {
- return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value));
- } else {
- const keys = new Set<string>();
- docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
- return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
- }
+ const keys = new Set<string>();
+ docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key)));
+ return Array.from(keys).filter(key => key.toLowerCase().startsWith(value));
}
getSuggestionValue = (suggestion: string) => suggestion;
@@ -157,9 +121,8 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
return (null);
}
componentDidMount() {
-
this._suggestionDispser = reaction(() => this._currentKey,
- () => this.getKeySuggestions(this._currentKey).then(action((s: string[]) => this._allSuggestions = s)),
+ () => this._allSuggestions = this.getKeySuggestions(this._currentKey),
{ fireImmediately: true });
}
componentWillUnmount() {
@@ -171,19 +134,8 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
}
private get considerChildOptions() {
- let docSource = this.props.docs;
- if (typeof docSource === "function") {
- docSource = docSource();
- }
- docSource = docSource as Doc[] | Doc;
- if (docSource instanceof Doc) {
- if (docSource._viewType === undefined) {
- return (null);
- }
- } else if (Array.isArray(docSource)) {
- if (!docSource.every(doc => doc._viewType !== undefined)) {
- return null;
- }
+ if (!this.props.docs.every(doc => doc._viewType !== undefined)) {
+ return null;
}
return (
<div style={{ display: "flex" }}>
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index 26c2e0e1e..09a349012 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -3,6 +3,8 @@
overflow: hidden;
display: flex;
flex-direction: column;
+ top: 0;
+ left: 0;
}
.overlayWindow-outerDiv,
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 37d8dd23b..5c3a8185c 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -124,7 +124,9 @@ export class OverlayView extends React.Component {
ele = <div key={Utils.GenerateGuid()} className="overlayView-wrapperDiv" style={{
transform: `translate(${options.x}px, ${options.y}px)`,
width: options.width,
- height: options.height
+ height: options.height,
+ top: 0,
+ left: 0
}}>{ele}</div>;
this._elements.push(ele);
return remove;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 916e631d0..9fb8a227e 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -108,8 +108,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
return100 = () => 100;
@computed get scriptField() {
- return ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
+ const script = ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
{ docs: new List<Doc>(this.props.docViews.map(dv => dv.props.Document)) });
+ return script ? () => script : undefined;
}
templateIsUsed = (selDoc: Doc, templateDoc: Doc) => {
const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
@@ -142,8 +143,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
ContainingCollectionView={undefined}
docFilters={returnEmptyFilter}
rootSelected={returnFalse}
- onCheckedClick={this.scriptField!}
- onChildClick={this.scriptField!}
+ onCheckedClick={this.scriptField}
+ onChildClick={this.scriptField}
LibraryPath={emptyPath}
dropAction={undefined}
active={returnTrue}
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index f54bd3aff..eac30ca75 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -385,10 +385,10 @@ export class Timeline extends React.Component<FieldViewProps> {
const ttime = `Total: ${this.toReadTime(this._time)}`;
return (
<div style={{ flexDirection: "column" }}>
- <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+ <div className="animation-text" style={{ fontSize: "10pt", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
{ctime}
</div>
- <div className="animation-text" style={{ fontSize: "10px", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
+ <div className="animation-text" style={{ fontSize: "10pt", width: "100%", display: !this.props.Document.isATOn ? "block" : "none" }}>
{ttime}
</div>
</div>
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 1344b70f4..8e9970ada 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -38,15 +38,15 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
panelWidth = () => this.props.PanelWidth() / 3;
panelHeight = () => this.props.PanelHeight() * 0.6;
+ onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
@computed get content() {
const currentIndex = NumCast(this.layoutDoc._itemIndex);
const displayDoc = (childPair: { layout: Doc, data: Doc }) => {
+ const script = ScriptField.MakeScript("child._showCaption = 'caption'", { child: Doc.name }, { child: childPair.layout });
+ const onChildClick = script && (() => script);
return <ContentFittingDocumentView {...this.props}
- onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
- onClick={ScriptField.MakeScript(
- "child._showCaption = 'caption'",
- { child: Doc.name },
- { child: childPair.layout })}
+ onDoubleClick={this.onChildDoubleClick}
+ onClick={onChildClick}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.ChildLayoutTemplate}
LayoutTemplateString={this.props.ChildLayoutString}
@@ -108,17 +108,6 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
}, 1500);
}
- onContextMenu = (e: React.MouseEvent): void => {
- // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped()) {
- ContextMenu.Instance.addItem({
- description: "Make Hero Image", event: () => {
- const index = NumCast(this.layoutDoc._itemIndex);
- (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
- }, icon: "plus"
- });
- }
- }
_downX = 0;
_downY = 0;
onPointerDown = (e: React.PointerEvent) => {
@@ -184,7 +173,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
const index = NumCast(this.layoutDoc._itemIndex);
const translateX = (1 - index) / this.childLayoutPairs.length * 100;
- return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+ return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
<div className="carousel-wrapper" style={{ transform: `translateX(calc(${translateX}%` }}>
{this.content}
</div>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index bd0e4fc9a..404dc0daa 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -14,6 +14,7 @@ import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { ContextMenu } from '../ContextMenu';
import { ObjectField } from '../../../fields/ObjectField';
import { returnFalse } from '../../../Utils';
+import { ScriptField } from '../../../fields/ScriptField';
type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
const CarouselDocument = makeInterface(documentSchema, collectionSchema);
@@ -40,14 +41,16 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
}
panelHeight = () => this.props.PanelHeight() - 50;
+ onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+ onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
@computed get content() {
const index = NumCast(this.layoutDoc._itemIndex);
return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
<>
<div className="collectionCarouselView-image" key="image">
<ContentFittingDocumentView {...this.props}
- onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
- onClick={ScriptCast(this.layoutDoc.onChildClick)}
+ onDoubleClick={this.onContentDoubleClick}
+ onClick={this.onContentClick}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.ChildLayoutTemplate}
LayoutTemplateString={this.props.ChildLayoutString}
@@ -83,18 +86,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
</>;
}
-
- onContextMenu = (e: React.MouseEvent): void => {
- // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped()) {
- ContextMenu.Instance?.addItem({
- description: "Make Hero Image", event: () => {
- const index = NumCast(this.layoutDoc._itemIndex);
- (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
- }, icon: "plus"
- });
- }
- }
_downX = 0;
_downY = 0;
onPointerDown = (e: React.PointerEvent) => {
@@ -119,7 +110,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
}
render() {
- return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+ return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
{this.content}
{this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
</div>;
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 18d642510..1895c06a1 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,5 +1,23 @@
@import "../../views/globalCssVariables.scss";
+.miniMap {
+ position: absolute;
+ overflow: hidden;
+ right: 10;
+ bottom: 10;
+ border: solid 1px;
+ box-shadow: black 0.4vw 0.4vw 0.8vw;
+
+ .miniOverlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ .miniThumb {
+ background: #25252525;
+ position: absolute;
+ }
+ }
+}
.lm_title {
margin-top: 3px;
border-radius: 5px;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 79c577b6d..be1ef56d0 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { FieldId } from "../../../fields/RefField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse, emptyPath, aggregateBounds } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
@@ -28,6 +28,9 @@ import { DockingViewButtonSelector } from './ParentDocumentSelector';
import React = require("react");
import { CollectionViewType } from './CollectionView';
import { SnappingManager } from '../../util/SnappingManager';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { listSpec } from '../../../fields/Schema';
+import { clamp } from 'lodash';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -390,6 +393,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
reactionDisposer?: Lambda;
componentDidMount: () => void = () => {
if (this._containerRef.current) {
+ const observer = new _global.ResizeObserver(action((entries: any) => {
+ for (const entry of entries) {
+ this.onResize(null as any);
+ }
+ }));
+ observer.observe(this._containerRef.current);
this.reactionDisposer = reaction(
() => this.props.Document.dockingConfig,
() => {
@@ -430,7 +439,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const cur = this._containerRef.current;
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
- this._goldenLayout && this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+ this._goldenLayout?.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
}
@action
@@ -646,16 +655,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (this.props.renderDepth > 0) {
return <div style={{ width: "100%", height: "100%" }}>Nested workspaces can't be rendered</div>;
}
- return (
- <Measure offset onResize={this.onResize}>
- {({ measureRef }) =>
- <div ref={measureRef}>
- <div className="collectiondockingview-container" id="menuContainer"
- onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
- </div>
- }
- </Measure>
- );
+ return <div className="collectiondockingview-container" id="menuContainer"
+ onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />;
}
}
@@ -664,7 +665,6 @@ interface DockedFrameProps {
documentId: FieldId;
glContainer: any;
libraryPath: (FieldId[]);
- backgroundColor?: (doc: Doc) => string | undefined;
//collectionDockingView: CollectionDockingView
}
@observer
@@ -818,35 +818,106 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
}
+ @computed get renderContentBounds() {
+ const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
+ const xbounds = bounds[2] - bounds[0];
+ const ybounds = bounds[3] - bounds[1];
+ const dim = Math.max(xbounds, ybounds);
+ return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
+ }
+ @computed get miniLeft() { return 50 + (NumCast(this._document?._panX) - this.renderContentBounds.cx) / this.renderContentBounds.dim * 100 - this.miniWidth / 2; }
+ @computed get miniTop() { return 50 + (NumCast(this._document?._panY) - this.renderContentBounds.cy) / this.renderContentBounds.dim * 100 - this.miniHeight / 2; }
+ @computed get miniWidth() { return this.panelWidth() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+ @computed get miniHeight() { return this.panelHeight() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+ returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
+ miniDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+ this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim);
+ this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim);
+ return false;
+ }), emptyFunction, emptyFunction);
+ }
+ renderMiniMap() {
+ return <div className="miniMap" style={{
+ width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor,
+ StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!))),
+ }}>
+ <CollectionFreeFormView
+ Document={this._document!}
+ LibraryPath={emptyPath}
+ CollectionView={undefined}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ active={returnTrue}
+ select={emptyFunction}
+ dropAction={undefined}
+ isSelected={returnFalse}
+ dontRegisterView={true}
+ annotationsKey={""}
+ fieldKey={Doc.LayoutFieldKey(this._document!)}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ ContentScaling={returnOne}
+ PanelWidth={this.returnMiniSize}
+ PanelHeight={this.returnMiniSize}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+ addDocTab={this.addDocTab}
+ pinToPres={DockedFrameRenderer.PinDoc}
+ docFilters={returnEmptyFilter}
+ fitToBox={true}
+ />
+ <div className="miniOverlay" onPointerDown={this.miniDown} >
+ <div className="miniThumb" style={{
+ width: `${this.miniWidth}%`,
+ height: `${this.miniHeight}%`,
+ left: `${this.miniLeft}%`,
+ top: `${this.miniTop}%`,
+ }}
+ />
+ </div>
+ </div>;
+ }
@computed get docView() {
TraceMobx();
if (!this._document) return (null);
const document = this._document;
- const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;// document.layout instanceof Doc ? document : this._dataDoc;
- return <DocumentView key={document[Id]}
- LibraryPath={this._libraryPath}
- Document={document}
- DataDoc={resolvedDataDoc}
- bringToFront={emptyFunction}
- rootSelected={returnTrue}
- addDocument={undefined}
- removeDocument={undefined}
- ContentScaling={this.contentScaling}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- NativeHeight={this.nativeHeight}
- NativeWidth={this.nativeWidth}
- ScreenToLocalTransform={this.ScreenToLocalTransform}
- renderDepth={0}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- focus={emptyFunction}
- backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
- addDocTab={this.addDocTab}
- pinToPres={DockedFrameRenderer.PinDoc}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />;
+ const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;
+ return <>
+ <DocumentView key={document[Id]}
+ LibraryPath={this._libraryPath}
+ Document={document}
+ DataDoc={resolvedDataDoc}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={undefined}
+ removeDocument={undefined}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ NativeHeight={this.nativeHeight}
+ NativeWidth={this.nativeWidth}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+ addDocTab={this.addDocTab}
+ pinToPres={DockedFrameRenderer.PinDoc}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ {document._viewType === CollectionViewType.Freeform ? this.renderMiniMap() : (null)}
+ </>;
}
render() {
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index c370415be..407524353 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -16,6 +16,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup';
+import { Tooltip } from '@material-ui/core';
type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -110,17 +111,22 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
const flexDir: any = StrCast(this.Document.flexDirection);
const backgroundColor = StrCast(this.props.Document.backgroundColor, "black");
const color = StrCast(this.props.Document.color, "white");
+
+ const menuOpener = <label htmlFor={`${guid}`} style={{
+ background: backgroundColor === color ? "black" : backgroundColor,
+ // width: "18px", height: "18px", fontSize: "12.5px",
+ // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
+ // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
+ }}
+ onPointerDown={e => e.stopPropagation()} >
+ <p>+</p>
+ </label>;
+
return <div className="collectionLinearView-outer">
<div className="collectionLinearView" ref={this.createDashEventsTarget} >
- <label htmlFor={`${guid}`} title="Close Menu" style={{
- background: backgroundColor === color ? "black" : backgroundColor,
- // width: "18px", height: "18px", fontSize: "12.5px",
- // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
- // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
- }}
- onPointerDown={e => e.stopPropagation()} >
- <p>+</p>
- </label>
+ <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></>} placement="top">
+ {menuOpener}
+ </Tooltip>
<input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
@@ -170,12 +176,21 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
}}
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
- Creating link from: {DocumentLinksButton.StartLink.title} </span>
- <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}
- > Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ Creating link from: {DocumentLinksButton.StartLink.title}
</span>
- <span className="bottomPopup-exit" onClick={this.exitLongLinks}
- >Exit</span>
+
+ <Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
+ "Turn on description pop-up"} </div></>} placement="top">
+ <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
+ Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ </span>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Exit link clicking mode </div></>} placement="top">
+ <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
+ Clear
+ </span>
+ </Tooltip>
{/* <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
onClick={this.exitLongLinks} /> */}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionMenu.scss
index b1e8d20ad..348b9e6ea 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,22 +1,36 @@
@import "../globalCssVariables";
-@import '~js-datepicker/dist/datepicker.min.css';
-.collectionViewChrome-cont {
- position: absolute;
+
+.collectionMenu-cont {
+ position:relative;
+ display:inline-flex;
width: 100%;
opacity: 0.9;
z-index: 9001;
transition: top .5s;
- background: lightgrey;
+ background: #121721;
+ color: white;
transform-origin: top left;
+ top: 0;
+ width:100%;
+
+ .antimodeMenu-button {
+ padding:0;
+ width:40px;
+ display: flex;
+ svg {
+ margin:auto;
+ }
+ }
- .collectionViewChrome {
+ .collectionMenu {
display: flex;
padding-bottom: 1px;
height: 32px;
border-bottom: .5px solid rgb(180, 180, 180);
overflow: visible;
z-index: 9001;
+ border: unset;
.collectionViewBaseChrome {
display: flex;
@@ -25,8 +39,8 @@
font-size: 75%;
//text-transform: uppercase;
//letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
+ background: #121721;
+ color: white;
outline-color: black;
border: none;
//padding: 12px 10px 11px 10px;
@@ -52,21 +66,21 @@
margin-left: 3px;
margin-right: 0px;
font-size: 75%;
- background: rgb(238, 238, 238);
+ background: #121721;
border: none;
- color: grey;
+ color: white;
}
.commandEntry-outerDiv {
pointer-events: all;
- background-color: gray;
+ background-color: #121721;
display: flex;
flex-direction: row;
height: 30px;
.commandEntry-drop {
color: white;
- width: 25px;
+ width: 30px;
margin-top: auto;
margin-bottom: auto;
}
@@ -94,13 +108,7 @@
color: grey;
margin-top: auto;
margin-bottom: auto;
- margin-left: 5px;
- }
-
- .collectionViewBaseChrome-viewModes {
- margin-left: 25px;
}
-
.collectionViewBaseChrome-viewSpecs {
margin-left: 5px;
display: grid;
@@ -109,7 +117,7 @@
position: relative;
display: flex;
margin: auto;
- background: gray;
+ background: #121721;
color: white;
width: 30px;
height: 30px;
@@ -190,8 +198,8 @@
font-size: 75%;
//text-transform: uppercase;
//letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
+ background: #121721;
+ color: white;
outline-color: black;
border: none;
//padding: 12px 10px 11px 10px;
@@ -297,7 +305,7 @@
}
}
-.collectionFreeFormViewChrome-cont {
+.collectionFreeFormMenu-cont {
width: 60px;
display: flex;
position: relative;
@@ -311,7 +319,7 @@
width: 20;
height: 30;
bottom: 0;
- background: gray;
+ background: #121721;
display: flex;
align-items: center;
color: white;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionMenu.tsx
index 7f1fe7649..cdd653823 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,39 +1,63 @@
+import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction, Lambda } from "mobx";
+import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
import { observer } from "mobx-react";
-import * as React from "react";
import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils";
-import { DragManager } from "../../util/DragManager";
+import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
+import AntimodeMenu from "../AntimodeMenu";
+import "./CollectionMenu.scss";
import { undoBatch } from "../../util/UndoManager";
-import { EditableView } from "../EditableView";
-import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
-import { CollectionViewType } from "./CollectionView";
-import { CollectionView } from "./CollectionView";
-import "./CollectionViewChromes.scss";
+import { CollectionViewType, CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
+import { List } from "../../../fields/List";
+import { EditableView } from "../EditableView";
+import { Id } from "../../../fields/FieldSymbols";
+import { listSpec } from "../../../fields/Schema";
-interface CollectionViewChromeProps {
- CollectionView: CollectionView;
- type: CollectionViewType;
- collapse?: (value: boolean) => any;
- PanelWidth: () => number;
+@observer
+export default class CollectionMenu extends AntimodeMenu {
+ static Instance: CollectionMenu;
+
+ @observable SelectedCollection: CollectionView | undefined;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ 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);
+ }
+
+ @action
+ toggleMenuPin = (e: React.MouseEvent) => {
+ Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
+ if (!this.Pinned && this._left < 0) {
+ this.jumpTo(300, 300);
+ }
+ }
+
+ render() {
+ const button = <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
+ <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+ </button>;
+
+ return this.getElement(!this.SelectedCollection ? [button] :
+ [<CollectionViewBaseChrome key="chrome" CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+ button]);
+ }
}
-interface Filter {
- key: string;
- value: string;
- contains: boolean;
+interface CollectionMenuProps {
+ CollectionView: CollectionView;
+ type: CollectionViewType;
}
const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@observer
-export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionViewBaseChrome extends React.Component<CollectionMenuProps> {
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
get target() { return this.props.CollectionView.props.Document; }
@@ -99,14 +123,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
componentDidMount = action(() => {
this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : "");
- // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
- switch (this.props.CollectionView.props.Document._chromeStatus) {
- case "disabled":
- throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
- case "collapsed":
- this.props.collapse?.(true);
- break;
- }
});
@undoBatch
@@ -130,93 +146,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
this.document._facetWidth = 0;
}
- // @action
- // openDatePicker = (e: React.PointerEvent) => {
- // if (this._picker) {
- // this._picker.alwaysShow = true;
- // this._picker.show();
- // // TODO: calendar is offset when zoomed in/out
- // // this._picker.calendar.style.position = "absolute";
- // // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
- // // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
- // // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
- // // this._picker.calendar.style.left = x;
- // // this._picker.calendar.style.top = y;
-
- // e.stopPropagation();
- // }
- // }
-
- // <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
- // id={Utils.GenerateGuid()}
- // ref={this.datePickerRef}
- // value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
- // onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
- // onPointerDown={this.openDatePicker}
- // placeholder="Value" />
- // @action.bound
- // applyFilter = (e: React.MouseEvent) => {
- // const keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")";
- // const yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
- // const monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
- // const weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
- // const dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
- // let dateRestrictionScript = "";
- // if (this._dateValue instanceof Date) {
- // const lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
- // const upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
- // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
- // }
- // else {
- // const createdDate = new Date(this._dateValue);
- // if (!isNaN(createdDate.getTime())) {
- // const lowerBound = new Date(createdDate.getFullYear() - yearOffset, createdDate.getMonth() - monthOffset, createdDate.getDate() - dayOffset);
- // const upperBound = new Date(createdDate.getFullYear() + yearOffset, createdDate.getMonth() + monthOffset, createdDate.getDate() + dayOffset + 1);
- // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
- // }
- // }
- // const fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ?
- // `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
- // `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
- // "true";
-
- // this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
- // }
-
- // datePickerRef = (node: HTMLInputElement) => {
- // if (node) {
- // try {
- // this._picker = datepicker("#" + node.id, {
- // disabler: (date: Date) => date > new Date(),
- // onSelect: (instance: any, date: Date) => runInAction(() => {}), // this._dateValue = date),
- // dateSelected: new Date()
- // });
- // } catch (e) {
- // console.log("date picker exception:" + e);
- // }
- // }
- // }
-
-
- @action
- toggleCollapse = () => {
- this.document._chromeStatus = this.document._chromeStatus === "enabled" ? "collapsed" : "enabled";
- if (this.props.collapse) {
- this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled");
- }
- }
@computed get subChrome() {
- const collapsed = this.document._chromeStatus !== "enabled";
- if (collapsed) return null;
switch (this.props.type) {
- case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={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;
}
}
@@ -270,15 +209,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@computed get templateChrome() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
+ return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
<div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
<div className="commandEntry-drop">
<FontAwesomeIcon icon="bullseye" size="2x" />
</div>
<select
- className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}
- style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}>
+ className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} />
{this._buttonizableCommands.map(cmd =>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option>
@@ -289,14 +226,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@computed get viewModes() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- return <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
+ return <div className="collectionViewBaseChrome-viewModes" >
<div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}>
<div className="commandEntry-drop">
<FontAwesomeIcon icon="bullseye" size="2x" />
</div>
<select
- className="collectionViewBaseChrome-viewPicker" style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}
+ className="collectionViewBaseChrome-viewPicker"
onPointerDown={stopPropagation}
onChange={this.viewChanged}
value={StrCast(this.props.CollectionView.props.Document._viewType)}>
@@ -315,29 +251,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
render() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
return (
- <div className="collectionViewChrome-cont" style={{
- top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined,
- transform: collapsed ? "" : `scale(${scale})`,
- width: `${this.props.PanelWidth() / scale}px`
- }}>
- <div className="collectionViewChrome" style={{ border: "unset", pointerEvents: collapsed ? "none" : undefined }}>
+ <div className="collectionMenu-cont" >
+ <div className="collectionMenu">
<div className="collectionViewBaseChrome">
- <button className="collectionViewBaseChrome-collapse"
- style={{
- top: collapsed ? 70 : 10,
- transform: `rotate(${collapsed ? 180 : 0}deg) scale(0.5) translate(${collapsed ? "-100%, -100%" : "0, 0"})`,
- opacity: 0.9,
- display: (collapsed && !this.props.CollectionView.props.isSelected()) ? "none" : undefined,
- left: (collapsed ? 0 : "unset"),
- }}
- title="Collapse collection chrome" onClick={this.toggleCollapse}>
- <FontAwesomeIcon icon="caret-up" size="2x" />
- </button>
{this.viewModes}
- <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
+ <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
<div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
<FontAwesomeIcon icon="filter" size="2x" />
</div>
@@ -352,7 +271,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps> {
get Document() { return this.props.CollectionView.props.Document; }
@computed get dataField() {
@@ -364,7 +283,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
@undoBatch
@action
nextKeyframe = (): void => {
- const currentFrame = NumCast(this.Document.currentFrame);
+ const currentFrame = Cast(this.Document.currentFrame, "number", null);
if (currentFrame === undefined) {
this.Document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -376,7 +295,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
@undoBatch
@action
prevKeyframe = (): void => {
- const currentFrame = NumCast(this.Document.currentFrame);
+ const currentFrame = Cast(this.Document.currentFrame, "number", null);
if (currentFrame === undefined) {
this.Document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -386,7 +305,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
}
render() {
return this.Document.isAnnotationOverlay ? (null) :
- <div className="collectionFreeFormViewChrome-cont">
+ <div className="collectionFreeFormMenu-cont">
<div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
<FontAwesomeIcon icon={"caret-left"} size={"lg"} />
</div>
@@ -400,9 +319,8 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
</div>;
}
}
-
@observer
-export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionStackingViewChrome extends React.Component<CollectionMenuProps> {
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
@@ -502,7 +420,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
@observer
-export class CollectionSchemaViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionSchemaViewChrome extends React.Component<CollectionMenuProps> {
// private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
@undoBatch
@@ -549,7 +467,7 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
}
@observer
-export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionTreeViewChrome extends React.Component<CollectionMenuProps> {
get sortAscending() {
return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
@@ -585,7 +503,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
// Enter scroll speed for 3D Carousel
@observer
-export class Collection3DCarouselViewChrome extends React.Component<CollectionViewChromeProps> {
+export class Collection3DCarouselViewChrome extends React.Component<CollectionMenuProps> {
@computed get scrollSpeed() {
return this.props.CollectionView.props.Document._autoScrollSpeed;
}
@@ -624,7 +542,7 @@ export class Collection3DCarouselViewChrome extends React.Component<CollectionVi
* Chrome for grid view.
*/
@observer
-export class CollectionGridViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionGridViewChrome extends React.Component<CollectionMenuProps> {
private clicked: boolean = false;
private entered: boolean = false;
@@ -770,7 +688,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
</span>
<select className="collectionGridViewChrome-viewPicker"
- style={{ marginRight: 5, width: this.props.PanelWidth() < 300 ? 25 : undefined }}
+ style={{ marginRight: 5 }}
onPointerDown={stopPropagation}
onChange={this.changeCompactType}
value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
@@ -797,4 +715,4 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ced234850..9b08fa9f2 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -163,8 +163,8 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
}
- @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
+ @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
+ @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
@@ -249,7 +249,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
return wid * aspect;
}
return layoutDoc._fitWidth ? !nh ? this.props.PanelHeight() - 2 * this.yMargin :
- Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
+ Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : Math.max(20, layoutDoc[HeightSym]());
}
columnDividerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ce6872695..3794088d4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx";
import { basename } from 'path';
import CursorField from "../../../fields/CursorField";
import { Doc, Opt, Field } from "../../../fields/Doc";
@@ -19,6 +19,7 @@ import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
import React = require("react");
import * as rp from 'request-promise';
+import ReactLoading from 'react-loading';
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc | Doc[]) => boolean;
@@ -377,13 +378,20 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
});
}
}
+ this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY);
+ batch.end();
+ }
+ slowLoadDocuments = async (files: File[], options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: (() => void) | undefined, clientX: number, clientY: number) => {
+ runInAction(() => CollectionSubViewLoader.Waiting = "block");
+ const disposer = OverlayView.Instance.addElement(
+ <ReactLoading type={"spinningBubbles"} color={"green"} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 });
generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options));
if (generatedDocuments.length) {
const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d));
if (set) {
- this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+ UndoManager.RunInBatch(() => this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!), "drop");
} else {
- generatedDocuments.forEach(this.addDocument);
+ UndoManager.RunInBatch(() => generatedDocuments.forEach(this.addDocument), "drop");
}
completed?.();
} else {
@@ -391,13 +399,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
}
}
- batch.end();
+ disposer();
}
}
return CollectionSubView;
}
+export class CollectionSubViewLoader {
+ @observable public static Waiting = "none";
+}
+
import { DragManager, dropActionType } from "../../util/DragManager";
import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -405,4 +417,5 @@ import { DocumentType } from "../../documents/DocumentTypes";
import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
import { CollectionView } from "./CollectionView";
import { SelectionManager } from "../../util/SelectionManager";
+import { OverlayView } from "../OverlayView";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 26c41f524..96a2e23c9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -61,8 +61,8 @@ export interface TreeViewProps {
treeViewHideHeaderFields: () => boolean;
treeViewPreventOpen: boolean;
renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
- onCheckedClick?: ScriptField;
- onChildClick?: ScriptField;
+ onCheckedClick?: () => ScriptField;
+ onChildClick?: () => ScriptField;
ignoreFields?: string[];
}
@@ -76,7 +76,7 @@ export interface TreeViewProps {
* treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
class TreeView extends React.Component<TreeViewProps> {
- private _editTitleScript: ScriptField | undefined;
+ private _editTitleScript: (() => ScriptField) | undefined;
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
@@ -124,7 +124,8 @@ class TreeView extends React.Component<TreeViewProps> {
constructor(props: any) {
super(props);
- this._editTitleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `);
+ const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `);
+ this._editTitleScript = script && (() => script);
if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false);
}
@@ -368,13 +369,13 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
- get onCheckedClick() { return this.props.onCheckedClick || ScriptCast(this.doc.onCheckedClick); }
+ get onCheckedClick() { return this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); }
@action
bulletClick = (e: React.MouseEvent) => {
if (this.onCheckedClick && this.doc.type !== DocumentType.COL) {
// this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
- this.onCheckedClick.script.run({
+ this.onCheckedClick?.script.run({
this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
heading: this.props.containingCollection.title,
checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check",
@@ -404,6 +405,7 @@ class TreeView extends React.Component<TreeViewProps> {
contextMenuItems = () => [{ script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }];
truncateTitleWidth = () => NumCast(this.props.treeViewDoc.treeViewTruncateTitleWidth, 0);
showTitleEdit = () => ["*", this._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || "");
+ onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.editTitleScript));
/**
* Renders the EditableView title element for placement into the tree.
*/
@@ -437,7 +439,7 @@ class TreeView extends React.Component<TreeViewProps> {
addDocTab={this.props.addDocTab}
rootSelected={returnTrue}
pinToPres={emptyFunction}
- onClick={this.props.onChildClick || this._editTitleScript}
+ onClick={this.onChildClick}
dropAction={this.props.dropAction}
moveDocument={this.move}
removeDocument={this.removeDoc}
@@ -539,8 +541,8 @@ class TreeView extends React.Component<TreeViewProps> {
treeViewPreventOpen: boolean,
renderedIds: string[],
libraryPath: Doc[] | undefined,
- onCheckedClick: ScriptField | undefined,
- onChildClick: ScriptField | undefined,
+ onCheckedClick: undefined | (() => ScriptField),
+ onChildClick: undefined | (() => ScriptField),
ignoreFields: string[] | undefined
) {
const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
@@ -658,8 +660,8 @@ class TreeView extends React.Component<TreeViewProps> {
export type collectionTreeViewProps = {
treeViewHideTitle?: boolean;
treeViewHideHeaderFields?: boolean;
- onCheckedClick?: ScriptField;
- onChildClick?: ScriptField;
+ onCheckedClick?: () => ScriptField;
+ onChildClick?: () => ScriptField;
};
@observer
@@ -780,7 +782,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
onClicks.push({
description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit"
});
- !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
@@ -797,6 +799,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
onKeyPress = (e: React.KeyboardEvent) => {
console.log(e);
}
+ onChildClick = () => {
+ return this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
+ }
render() {
TraceMobx();
if (!(this.doc instanceof Doc)) return (null);
@@ -839,7 +844,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
BoolCast(this.doc.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick,
- this.props.onChildClick || ScriptCast(this.doc.onChildClick), this.props.ignoreFields)
+ this.onChildClick, this.props.ignoreFields)
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index df21d6a28..c1da23470 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -47,7 +47,7 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
-import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import CollectionMenu from './CollectionMenu';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -140,17 +140,17 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
} else {
added.map(doc => {
const context = Cast(doc.context, Doc, null);
- if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG) &&
- !DocListCast(doc.links).some(d => d.isPushpin)) {
+ if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
const pushpin = Docs.Create.FontIconDocument({
+ title: "pushpin",
icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
_width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
});
+ pushpin.isPushpin = true;
+ Doc.GetProto(pushpin).annotationOn = doc.annotationOn;
+ Doc.SetInPlace(doc, "annotationOn", undefined, true);
Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
- const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
- pushpinLink && (Doc.GetProto(pushpinLink).isPushpin = true);
doc.displayTimecode = undefined;
}
doc.context = this.props.Document;
@@ -234,16 +234,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}
}
- @action
- private collapse = (value: boolean) => {
- this.props.Document._chromeStatus = value ? "collapsed" : "enabled";
- }
-
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
- // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
- const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) :
- <CollectionViewBaseChrome key="chrome" CollectionView={this} PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
- return <>{chrome} {this.SubViewHelper(type, renderProps)}</>;
+ return this.SubViewHelper(type, renderProps);
}
@@ -272,7 +264,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
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, subitems: subItems, icon: "eye" });
+ !existingVm && ContextMenu.Instance.addItem({ description: category, noexpand: true, subitems: subItems, icon: "eye" });
}
onContextMenu = (e: React.MouseEvent): void => {
@@ -294,7 +286,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
}
- layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+ !Doc.UserDoc().noviceMode && layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
!existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
@@ -305,16 +297,18 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
{ key: "onChildDoubleClick", name: "On Child Double Clicked" }];
funcs.map(func => onClicks.push({
description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
- ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name });
+ const alias = Doc.MakeAlias(this.props.Document);
+ DocUtils.makeCustomViewClicked(alias, undefined, func.key);
+ this.props.addDocTab(alias, "onRight");
}
}));
DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
onClicks.push({
description: `Set child ${childClick.title}`,
icon: "edit",
- event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
+ event: () => Doc.GetProto(this.props.Document)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
}));
- !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
if (!Doc.UserDoc().noviceMode) {
const more = cm.findByDescription("More...");
@@ -470,7 +464,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
@computed get scriptField() {
const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
- return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+ const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+ return script ? () => script : undefined;
}
@computed get filterView() {
TraceMobx();
@@ -523,7 +518,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ContentScaling={returnOne}
focus={returnFalse}
treeViewHideHeaderFields={true}
- onCheckedClick={this.scriptField!}
+ onCheckedClick={this.scriptField}
ignoreFields={this.ignoreFields}
annotationsKey={""}
dontRegisterView={true}
@@ -549,6 +544,7 @@ 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}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 05111adb4..8cbda310a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -16,4 +16,5 @@
stroke: rgb(0,0,0);
opacity: 0.5;
pointer-events: all;
+ cursor: move;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index a24693c30..6d44ac967 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,13 +1,12 @@
import { observer } from "mobx-react";
import { Doc } from "../../../../fields/Doc";
-import { Utils } from '../../../../Utils';
+import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from '../../../../Utils';
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinkView.scss";
import React = require("react");
-import v5 = require("uuid/v5");
import { DocumentType } from "../../../documents/DocumentTypes";
-import { observable, action, reaction, IReactionDisposer } from "mobx";
-import { StrCast, Cast } from "../../../../fields/Types";
+import { observable, action, reaction, IReactionDisposer, trace, computed } from "mobx";
+import { StrCast, Cast, NumCast } from "../../../../fields/Types";
import { Id } from "../../../../fields/FieldSymbols";
import { SnappingManager } from "../../../util/SnappingManager";
@@ -20,18 +19,27 @@ export interface CollectionFreeFormLinkViewProps {
@observer
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
@observable _opacity: number = 0;
+ @observable _start = 0;
_anchorDisposer: IReactionDisposer | undefined;
+ _timeout: NodeJS.Timeout | undefined;
+ componentWillUnmount() {
+ this._anchorDisposer?.();
+ }
@action
+ timeout = () => (Date.now() < this._start++ + 1000) && setTimeout(this.timeout, 25);
componentDidMount() {
this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
action(() => {
- if (SnappingManager.GetIsDragging()) return;
+ this._start = Date.now();
+ this._timeout && clearTimeout(this._timeout);
+ this._timeout = setTimeout(this.timeout, 25);
+ if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv) return;
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
- const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
- const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
+ const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv);
+ const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv);
const a = adiv.getBoundingClientRect();
const b = bdiv.getBoundingClientRect();
const abounds = adiv.parentElement!.getBoundingClientRect();
@@ -82,12 +90,26 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
})
, { fireImmediately: true });
}
- @action
- componentWillUnmount() {
- this._anchorDisposer?.();
+
+
+ pointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, (e, down, delta) => {
+ this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0];
+ this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1];
+ return false;
+ }, emptyFunction, () => {
+ // OverlayView.Instance.addElement(
+ // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]}
+ // showLinks={action(() => { })}
+ // />, { x: 300, y: 300 });
+ });
}
- render() {
- if (SnappingManager.GetIsDragging()) return null;
+
+ @computed get renderData() {
+ this._start;
+ if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv || !this.props.LinkDocs.length) {
+ return undefined;
+ }
this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
const acont = this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
const bcont = this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
@@ -105,18 +127,26 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const pt2vec = [pt2[0] - (b.left + b.width / 2), pt2[1] - (b.top + b.height / 2)];
const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
- const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 3;
+ const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
const pt1norm = [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
const pt2norm = [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- const text = StrCast(this.props.A.props.Document.linkRelationship);
- return !a.width || !b.width || ((!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <text x={(Math.min(pt1[0], pt2[0]) * 2 + Math.max(pt1[0], pt2[0])) / 3} y={(pt1[1] + pt2[1]) / 2}>
- {text !== "-ungrouped-" ? text : ""}
- </text>
+ const bActive = this.props.B.isSelected() || Doc.IsBrushed(this.props.B.props.Document);
+
+ const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(this.props.LinkDocs[0].linkOffsetX);
+ const textY = (pt1[1] + pt2[1]) / 2 + NumCast(this.props.LinkDocs[0].linkOffsetY);
+ return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 };
+ }
+
+ render() {
+ if (!this.renderData) return (null);
+ const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
+ return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
<path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
+ <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
+ {StrCast(this.props.LinkDocs[0].description)}
+ </text>
</>);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2b55008f4..45c5c6a40 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -86,6 +86,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private _lastY: number = 0;
private _downX: number = 0;
private _downY: number = 0;
+ private _lastClientY: number | undefined = 0;
+ private _lastClientX: number | undefined = 0;
private _inkToTextStartX: number | undefined;
private _inkToTextStartY: number | undefined;
private _wordPalette: Map<string, string> = new Map<string, string>();
@@ -103,6 +105,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@observable _clusterSets: (Doc[])[] = [];
@observable _timelineRef = React.createRef<Timeline>();
+ @observable _marqueeRef = React.createRef<HTMLDivElement>();
+ @observable canPanX: boolean = true;
+ @observable canPanY: boolean = true;
+
@computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
@computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
@@ -182,7 +188,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
}
public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
@@ -207,7 +213,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
- if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ if (this.Document.currentFrame !== undefined) {
const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.opacity);
} else {
@@ -577,6 +583,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
onPointerUp = (e: PointerEvent): void => {
+ this._lastClientY = this._lastClientX = undefined;
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
document.removeEventListener("pointermove", this.onPointerMove);
@@ -935,9 +942,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
- @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
@computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+ onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
backgroundHalo = () => BoolCast(this.Document.useClusters);
parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
@@ -1030,7 +1037,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const transform = `translate(${x}px, ${y}px)`;
if (viewDef.type === "text") {
const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
- const fontSize = Cast(viewDef.fontSize, "number");
+ const fontSize = Cast(viewDef.fontSize, "string");
return [text, x, y].some(val => val === undefined) ? undefined :
{
ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
@@ -1068,7 +1075,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
doFreeformLayout(poolData: Map<string, PoolData>) {
const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
- const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
+ const initResult = this.Document.arrangeInit?.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
const state = initResult?.success ? initResult.result.scriptState : undefined;
const elements = initResult?.success ? this.viewDefsToJSX(initResult.result.views) : [];
@@ -1146,10 +1153,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
+
+ const handler = (e: any) => this.handleDragging(e, (e as CustomEvent<DragEvent>).detail);
+
+ document.addEventListener("dashDragging", handler);
}
+
componentWillUnmount() {
this._layoutComputeReaction?.();
+
+ const handler = (e: any) => this.handleDragging(e, (e as CustomEvent<DragEvent>).detail);
+ document.removeEventListener("dashDragging", handler);
}
+
@computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
elementFunc = () => this._layoutElements;
@@ -1158,6 +1174,43 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+
+ // <div ref={this._marqueeRef}>
+
+ @action
+ handleDragging = (e: CustomEvent<React.DragEvent>, de: DragEvent) => {
+ if ((e as any).handlePan) return;
+ (e as any).handlePan = true;
+ this._lastClientY = e.detail.clientY;
+ this._lastClientX = e.detail.clientX;
+
+ if (this._marqueeRef?.current) {
+ const dragX = e.detail.clientX;
+ const dragY = e.detail.clientY;
+ const bounds = this._marqueeRef.current?.getBoundingClientRect();
+
+ const deltaX = dragX - bounds.left < 25 ? -2 : bounds.right - dragX < 25 ? 2 : 0;
+ const deltaY = dragY - bounds.top < 25 ? -2 : bounds.bottom - dragY < 25 ? 2 : 0;
+ (deltaX !== 0 || deltaY !== 0) && this.continuePan(deltaX, deltaY);
+ }
+ e.stopPropagation();
+ }
+
+ continuePan = (deltaX: number, deltaY: number) => {
+ setTimeout(action(() => {
+ const dragY = this._lastClientY;
+ const dragX = this._lastClientX;
+ if (dragY !== undefined && dragX !== undefined && this._marqueeRef.current) {
+ const bounds = this._marqueeRef.current.getBoundingClientRect();
+ this.Document._panY = NumCast(this.Document._panY) + deltaY;
+ this.Document._panX = NumCast(this.Document._panX) + deltaX;
+ if (dragY - bounds.top < 25 || bounds.bottom - dragY < 25 || dragX - bounds.left < 25 || bounds.right - dragX < 25) {
+ this.continuePan(deltaX, deltaY);
+ }
+ } else this._lastClientY !== undefined && this._lastClientX !== undefined && this.continuePan(deltaX, deltaY);
+ }), 50);
+ }
+
promoteCollection = undoBatch(action(() => {
const childDocs = this.childDocs.slice();
childDocs.forEach(doc => {
@@ -1339,7 +1392,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return false;
});
@computed get marqueeView() {
- return <MarqueeView {...this.props}
+ return <MarqueeView
+ {...this.props}
nudge={this.nudge}
addDocTab={this.addDocTab}
activeDocuments={this.getActiveDocuments}
@@ -1349,16 +1403,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getContainerTransform={this.getContainerTransform}
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
- <CollectionFreeFormViewPannableContents
- centeringShiftX={this.centeringShiftX}
- centeringShiftY={this.centeringShiftY}
- presPaths={BoolCast(this.Document.presPathView)}
- // progressivize={BoolCast(this.Document.editProgressivize)}
- transition={Cast(this.layoutDoc._viewTransition, "string", null)}
- viewDefDivClick={this.props.viewDefDivClick}
- zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
- {this.children}
- </CollectionFreeFormViewPannableContents>
+ <div ref={this._marqueeRef}>
+ <CollectionFreeFormViewPannableContents
+ centeringShiftX={this.centeringShiftX}
+ centeringShiftY={this.centeringShiftY}
+ presPaths={BoolCast(this.Document.presPathView)}
+ // progressivize={BoolCast(this.Document.editProgressivize)}
+ transition={Cast(this.layoutDoc._viewTransition, "string", null)}
+ viewDefDivClick={this.props.viewDefDivClick}
+ zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ {this.children}
+ </CollectionFreeFormViewPannableContents></div>
{this.showTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
</MarqueeView>;
}
@@ -1375,6 +1430,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
+ !this.fitToContent && this._layoutElements?.length && setTimeout(() => this.Document._renderContentBounds = new List<number>([this.contentBounds.x, this.contentBounds.y, this.contentBounds.r, this.contentBounds.b]), 0);
return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
onPointerOver={this.onPointerOver}
onWheel={this.onPointerWheel}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
new file mode 100644
index 000000000..88876471c
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
@@ -0,0 +1,64 @@
+.antimodeMenu-button {
+ width: 200px;
+ position: relative;
+ text-align: left;
+
+ .color-previewI {
+ width: 100%;
+ height: 40%;
+ }
+
+ .color-previewII {
+ width: 100%;
+ height: 100%;
+ }
+}
+
+.antimenu-Buttonup {
+ position: absolute;
+ width: 20;
+ height: 10;
+ right: 0;
+ padding: 0;
+}
+
+.formatShapePane-inputBtn {
+ width: inherit;
+ position: absolute;
+}
+
+.sketch-picker {
+ background: #323232;
+
+ .flexbox-fit {
+ background: #323232;
+ }
+}
+
+.btn-group {
+ display: grid;
+ grid-template-columns: auto auto auto auto;
+ /* Make the buttons appear below each other */
+}
+
+.btn-group-palette {
+ display: block;
+ /* Make the buttons appear below each other */
+}
+
+.btn-draw {
+ display: inline;
+ /* Make the buttons appear below each other */
+}
+
+.btn2-group {
+ display: block;
+ background: #323232;
+ grid-template-columns: auto;
+
+ /* Make the buttons appear below each other */
+ .antimodeMenu-button {
+ background: #323232;
+ display: block;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
new file mode 100644
index 000000000..4e328d838
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -0,0 +1,307 @@
+import React = require("react");
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, Field, Opt } from "../../../../fields/Doc";
+import { Document } from "../../../../fields/documentSchemas";
+import { InkField } from "../../../../fields/InkField";
+import { BoolCast, Cast, NumCast } from "../../../../fields/Types";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { SelectionManager } from "../../../util/SelectionManager";
+import AntimodeMenu from "../../AntimodeMenu";
+import "./FormatShapePane.scss";
+import { undoBatch } from "../../../util/UndoManager";
+
+@observer
+export default class FormatShapePane extends AntimodeMenu {
+ static Instance: FormatShapePane;
+
+ 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 _currMode = "fill-drip";
+ @observable private _lock = false;
+ @observable private _fillBtn = false;
+ @observable private _lineBtn = false;
+
+ getField(key: string) {
+ return this.selectedInk?.reduce((p, i) =>
+ (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
+ }
+
+ @computed get selectedInk() {
+ const inks = SelectionManager.SelectedDocuments().filter(i => Document(i.rootDoc).type === DocumentType.INK);
+ return inks.length ? inks : undefined;
+ }
+ @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
+ @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
+ @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
+ @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
+ @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
+ @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
+ @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
+ @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+ @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+ @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+ @computed get shapeHgt() { return this.getField("_height"); }
+ @computed get shapeWid() { return this.getField("_width"); }
+ @computed get shapeXps() { return this.getField("x"); }
+ @computed get shapeYps() { return this.getField("y"); }
+ @computed get shapeRot() { return this.getField("rotation"); }
+ set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
+ set solidFil(value) { this.unFilled = !value; }
+ set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
+ set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
+ set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
+ set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
+ set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+ set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+ set dashdStk(value) {
+ value && (this._lastDash = value) && (this.unStrokd = false);
+ this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
+ }
+ set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
+ set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
+ set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
+ set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
+ set shapeWid(value) {
+ this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldWidth = NumCast(i.rootDoc._width);
+ i.rootDoc._width = Number(value);
+ this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
+ });
+ }
+ set shapeHgt(value) {
+ this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldHeight = NumCast(i.rootDoc._height);
+ i.rootDoc._height = Number(value);
+ this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
+ });
+ }
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ FormatShapePane.Instance = this;
+ this._canFade = false;
+ this.Pinned = BoolCast(Doc.UserDoc()["menuFormatShape-pinned"]);
+ }
+
+ @action
+ closePane = () => {
+ this.fadeOut(false);
+ this.Pinned = false;
+ }
+
+ @action
+ upDownButtons = (dirs: string, field: string) => {
+ switch (field) {
+ case "rot": this.rotate((dirs === "up" ? .1 : -.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 => {
+ const oldWidth = NumCast(i.rootDoc._width);
+ i.rootDoc._width = oldWidth + (dirs === "up" ? 10 : - 10);
+ this._lock && (i.rootDoc._height = (i.rootDoc._width / oldWidth * NumCast(i.rootDoc._height)));
+ });
+ break;
+ case "hgt": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldHeight = NumCast(i.rootDoc._height);
+ i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
+ this._lock && (i.rootDoc._width = (i.rootDoc._height / oldHeight * NumCast(i.rootDoc._width)));
+ });
+ break;
+ }
+ }
+
+ @undoBatch
+ @action
+ rotate = (degrees: number) => {
+ this.selectedInk?.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);
+ const ys = ink.map(p => p.Y);
+ const left = Math.min(...xs);
+ 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 });
+
+ 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;
+ 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;
+ }
+ }
+ }));
+ }
+
+
+ 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>)}
+ </div>;
+ }
+ inputBox = (key: string, value: any, setter: (val: string) => {}) => {
+ return <>
+ <input style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+ type="text" value={value}
+ onChange={e => setter(e.target.value)}
+ autoFocus />
+ <button className="antiMenu-Buttonup" key="up" 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>
+ </>;
+ }
+
+ 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>
+ <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); }
+
+ @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color); }
+ @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color); }
+
+ @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 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 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>;
+
+ 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 />
+ {(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 />
+ </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;
+ return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}>
+ {subMenus.map((subMenu, i) =>
+ <div key={subMenu} style={{ width: "inherit" }}>
+ <button className="antimodeMenu-button" onPointerDown={action(() => this._subOpen[i + indexOffset] = !this._subOpen[i + indexOffset])}
+ style={{ backgroundColor: "121212", position: "relative", width: "inherit" }}>
+ {this._subOpen[i + indexOffset] ? "▼" : "▶︎"}
+ {subMenu}
+ </button>
+ {menuItems[i]}
+ </div>)}
+ </div>;
+ }
+
+ @computed get closeBtn() {
+ return <button className="antimodeMenu-button" key="close" onPointerDown={action(() => this.closePane())} style={{ position: "absolute", right: 0 }}>
+ X
+ </button>;
+ }
+
+ @computed get propertyGroupBtn() {
+ return <div className="antimodeMenu-button-tab" key="modes">
+ {this._mode.map(mode =>
+ <button className="antimodeMenu-button" key={mode} onPointerDown={action(() => this._currMode = mode)}
+ style={{ backgroundColor: this._currMode === mode ? "121212" : "", position: "relative", top: 30 }}>
+ <FontAwesomeIcon icon={mode as IconProp} size="lg" />
+ </button>)}
+ </div>;
+ }
+
+ render() {
+ return this.getElementVert([this.closeBtn, this.propertyGroupBtn, this.propertyGroupItems]);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
index 753de6bef..03c6c97ab 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
@@ -25,8 +25,13 @@
/* Make the buttons appear below each other */
}
+.btn-draw {
+ display: inline;
+ /* Make the buttons appear below each other */
+}
+
.btn2-group {
- display: block;
+ display: grid;
background: #323232;
grid-template-columns: auto;
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
index f47fca6ac..23e1c194a 100644
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
@@ -3,7 +3,7 @@ import AntimodeMenu from "../../AntimodeMenu";
import { observer } from "mobx-react";
import { observable, action, computed } from "mobx";
import "./InkOptionsMenu.scss";
-import { ActiveInkColor, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
+import { ActiveInkColor, ActiveFillColor, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
import { Scripting } from "../../../util/Scripting";
import { InkTool } from "../../../../fields/InkField";
import { ColorState } from "react-color";
@@ -14,52 +14,41 @@ import { SelectionManager } from "../../../util/SelectionManager";
import { DocumentView } from "../../../views/nodes/DocumentView";
import { Document } from "../../../../fields/documentSchemas";
import { DocumentType } from "../../../documents/DocumentTypes";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faSleigh, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve, } from "@fortawesome/free-solid-svg-icons";
-
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve);
-
-
+import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
+import { BoolCast } from "../../../../fields/Types";
+import FormatShapePane from "./FormatShapePane";
@observer
export default class InkOptionsMenu extends AntimodeMenu {
static Instance: InkOptionsMenu;
- private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", "none"];
+ private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
private _width = ["1", "5", "10", "100"];
- // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
- // private _icons = ["O", "∆", "ロ", "➜", "-"];
- private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",];
- private _icons = ["O", "∆", "ロ", "⎯", "✖︎", " "];
- //arrowStart and arrowEnd must match and defs must exist in Inking Stroke
- private _arrowStart = ["arrowHead", "arrowHead", "dot", "dot", "none"];
- private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"];
- private _arrowIcons = ["→", "↔︎", "•", "••", " "];
+ private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
+ private _head = ["", "", "arrow", "", "", "arrow", "", "", ""];
+ private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""];
+ private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"];
+
+ @observable _shapesNum = this._shape.length;
+ @observable _selected = this._shapesNum;
+
+ @observable _collapsed = false;
+ @observable _keepMode = false;
@observable _colorBtn = false;
@observable _widthBtn = false;
@observable _fillBtn = false;
- @observable _arrowBtn = false;
- @observable _dashBtn = false;
- @observable _shapeBtn = false;
-
-
constructor(props: Readonly<{}>) {
super(props);
InkOptionsMenu.Instance = this;
this._canFade = false; // don't let the inking menu fade away
- }
-
- getColors = () => {
- return this._palette;
+ this.Pinned = BoolCast(Doc.UserDoc()["menuInkOptions-pinned"]);
}
@action
- changeArrow = (arrowStart: string, arrowEnd: string) => {
- SetActiveArrowStart(arrowStart);
- SetActiveArrowEnd(arrowEnd);
+ toggleMenuPin = (e: React.MouseEvent) => {
+ Doc.UserDoc()["menuInkOptions-pinned"] = this.Pinned = !this.Pinned;
}
@action
@@ -81,252 +70,125 @@ export default class InkOptionsMenu extends AntimodeMenu {
const doc = Document(element.rootDoc);
if (doc.type === DocumentType.INK) {
switch (field) {
- case "width":
- doc.strokeWidth = Number(value);
- break;
- case "color":
- doc.color = String(value);
- break;
- case "fill":
- doc.fillColor = String(value);
- break;
- case "bezier":
- // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300;
- break;
- case "arrowStart":
- doc.arrowStart = String(value);
- break;
- case "arrowEnd":
- doc.arrowEnd = String(value);
- break;
- case "dash":
- doc.dash = Number(value);
- default:
- break;
+ case "width": doc.strokeWidth = Number(value); break;
+ case "color": doc.color = String(value); break;
+ case "fill": doc.fillColor = String(value); break;
+ case "dash": doc.strokeDash = value;
}
}
}));
}
-
- @action
- changeBezier = (e: React.PointerEvent): void => {
- SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : "");
- this.editProperties(0, "bezier");
- }
- @action
- changeDash = (e: React.PointerEvent): void => {
- SetActiveDash(ActiveDash() === "0" ? "2" : "0");
- this.editProperties(ActiveDash(), "dash");
- }
-
- @computed get arrowPicker() {
- var currIcon;
- for (var i = 0; i < this._arrowStart.length; i++) {
- if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) {
- currIcon = this._arrowIcons[i];
- if (this._arrowIcons[i] === " ") {
- currIcon = "➤";
- }
+ @computed get drawButtons() {
+ const func = action((i: number, keep: boolean) => {
+ this._keepMode = keep;
+ if (this._selected !== i) {
+ this._selected = i;
+ Doc.SetSelectedTool(InkTool.Pen);
+ SetActiveArrowStart(this._head[i]);
+ SetActiveArrowEnd(this._end[i]);
+ SetActiveBezierApprox("300");
+
+ GestureOverlay.Instance.InkShape = this._shape[i];
+ } else {
+ this._selected = this._shapesNum;
+ Doc.SetSelectedTool(InkTool.None);
+ SetActiveArrowStart("");
+ SetActiveArrowEnd("");
+ GestureOverlay.Instance.InkShape = "";
+ SetActiveBezierApprox("0");
}
- }
- var arrowPicker = <button
- className="antimodeMenu-button"
- key="arrow"
- onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)}
- style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
- {currIcon}
+ });
+ 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)}
+ style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
+ {this._draw[i]}
+ </button>)}
+ </div>;
+ }
+
+ toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => {
+ return <button className="antimodeMenu-button" key={key} title={key}
+ onPointerDown={action(e => setter())}
+ style={{ backgroundColor: value ? "121212" : "" }}>
+ <FontAwesomeIcon icon={icon} size="lg" />
+ {ele}
</button>;
- if (this._arrowBtn) {
- arrowPicker = <div className="btn2-group" key="arrows">
- {arrowPicker}
- {this._arrowStart.map((arrowStart, i) => {
- return <button
- className="antimodeMenu-button"
- key={arrowStart}
- onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })}
- style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
- {this._arrowIcons[i]}
- </button>;
- })}
- </div>;
- }
- return arrowPicker;
}
@computed get widthPicker() {
- var widthPicker = <button
- className="antimodeMenu-button"
- key="width"
- onPointerDown={action(e => this._widthBtn = !this._widthBtn)}
- style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="bars" size="lg" />
- </button>;
- if (this._widthBtn) {
- widthPicker = <div className="btn2-group" key="width">
+ var widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
+ return !this._widthBtn ? widthPicker :
+ <div className="btn2-group" key="width">
{widthPicker}
- {this._width.map(wid => {
- return <button
- className="antimodeMenu-button"
- key={wid}
+ {this._width.map(wid =>
+ <button className="antimodeMenu-button" key={wid}
onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
- style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
+ style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
{wid}
- </button>;
- })}
+ </button>)}
</div>;
- }
- return widthPicker;
}
-
-
@computed get colorPicker() {
- var colorPicker = <button
- className="antimodeMenu-button"
- key="color"
- title="colorChanger"
- onPointerDown={action(e => this._colorBtn = !this._colorBtn)}
- style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="pen-nib" size="lg" />
- <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }}></div>
-
- </button>;
- if (this._colorBtn) {
- colorPicker = <div className="btn-group" key="color">
+ var colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
+ <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />);
+ return !this._colorBtn ? colorPicker :
+ <div className="btn-group" key="color">
{colorPicker}
- {this._palette.map(color => {
- return <button
- className="antimodeMenu-button"
- key={color}
+ {this._palette.map(color =>
+ <button className="antimodeMenu-button" key={color}
onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
- style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
+ style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
{/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
- <div className="color-previewII" style={{ backgroundColor: color }}></div>
- </button>;
- })}
+ <div className="color-previewII" style={{ backgroundColor: color }} />
+ </button>)}
</div>;
- }
- return colorPicker;
}
-
@computed get fillPicker() {
- var fillPicker = <button
- className="antimodeMenu-button"
- key="fill"
- title="fillChanger"
- onPointerDown={action(e => this._fillBtn = !this._fillBtn)}
- style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="fill-drip" size="lg" />
- <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div>
- </button>;
- if (this._fillBtn) {
- fillPicker = <div className="btn-group" key="fill">
+ var fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
+ <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />);
+ return !this._fillBtn ? fillPicker :
+ <div className="btn-group" key="fill" >
{fillPicker}
- {this._palette.map(color => {
- return <button
- className="antimodeMenu-button"
- key={color}
+ {this._palette.map(color =>
+ <button className="antimodeMenu-button" key={color}
onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
- style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+ style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
<div className="color-previewII" style={{ backgroundColor: color }}></div>
- </button>;
- })}
-
- </div>;
- }
- return fillPicker;
- }
+ </button>)}
- @computed get shapePicker() {
- var currIcon;
- if (GestureOverlay.Instance.InkShape === "") {
- currIcon = <FontAwesomeIcon icon="shapes" size="lg" />;
- } else {
- for (var i = 0; i < this._icons.length; i++) {
- if (GestureOverlay.Instance.InkShape === this._buttons[i]) {
- currIcon = this._icons[i];
- }
- }
- }
- var shapePicker = <button
- className="antimodeMenu-button"
- key="shape"
- onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)}
- style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
- {currIcon}
- </button>;
- if (this._shapeBtn) {
- shapePicker = <div className="btn2-group" key="shape">
- {shapePicker}
- {this._buttons.map((btn, i) => {
- var ttl = btn;
- if (btn === "") {
- ttl = "no shape";
- }
- if (btn === "noRec") {
- ttl = "disable shape recognition";
- }
- return <button
- className="antimodeMenu-button"
- title={`Draw ${btn}`}
- key={ttl}
- onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })}
- style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
- {this._icons[i]}
- </button>;
- })}
</div>;
- }
- return shapePicker;
}
- @computed get bezierButton() {
- return <button
- className="antimodeMenu-button"
- title="Bezier changer"
- key="bezier"
- onPointerDown={e => this.changeBezier(e)}
- style={{ backgroundColor: ActiveInkBezierApprox() ? "121212" : "" }}>
- <FontAwesomeIcon icon="bezier-curve" size="lg" />
-
- </button>;
- }
-
- @computed get dashButton() {
- return <button
- className="antimodeMenu-button"
- title="dash changer"
- key="dash"
- onPointerDown={e => this.changeDash(e)}
- style={{ backgroundColor: ActiveDash() !== "0" ? "121212" : "" }}>
- <FontAwesomeIcon icon="ellipsis-h" size="lg" />
-
+ @computed get formatPane() {
+ 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" />
</button>;
}
render() {
- const buttons = [
- // <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}>
- // <FontAwesomeIcon icon="arrows-alt" size="lg" />
- // </button>,
- this.shapePicker,
- this.bezierButton,
+ return this.getElement([
this.widthPicker,
this.colorPicker,
this.fillPicker,
- this.arrowPicker,
- this.dashButton,
- ];
- return this.getElement(buttons);
+ this.drawButtons,
+ this.formatPane,
+ <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this._collapsed ? "none" : undefined }}>
+ <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+ </button>
+ ]);
}
}
Scripting.addGlobal(function activatePen(penBtn: any) {
if (penBtn) {
- Doc.SetSelectedTool(InkTool.Pen);
InkOptionsMenu.Instance.jumpTo(300, 300);
+ InkOptionsMenu.Instance.Pinned = true;
} else {
- Doc.SetSelectedTool(InkTool.None);
+ InkOptionsMenu.Instance.Pinned = false;
InkOptionsMenu.Instance.fadeOut(true);
}
});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b47236bea..2db665337 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -129,7 +129,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else if (!e.ctrlKey && !e.metaKey) {
FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
const tbox = Docs.Create.TextDocument("", {
- _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
+ _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
_fontFamily: StrCast(Doc.UserDoc().fontFamily),
title: "-typed text-"
});
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
index 9c2d5cbff..4d8473be9 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.scss
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -8,7 +8,6 @@
.collectionGridView-gridContainer {
height: 100%;
overflow-y: auto;
- background-color: white;
overflow-x: hidden;
display: flex;
@@ -22,7 +21,7 @@
}
.react-grid-layout {
- width : 100%;
+ width: 100%;
}
.react-grid-item>.react-resizable-handle {
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 2015ca930..21f77e47b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,7 +1,7 @@
import { action, computed, Lambda, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from "react";
-import { Doc, Opt } from '../../../../fields/Doc';
+import { Doc, Opt, WidthSym } from '../../../../fields/Doc';
import { documentSchema } from '../../../../fields/documentSchemas';
import { Id } from '../../../../fields/FieldSymbols';
import { makeInterface } from '../../../../fields/Schema';
@@ -30,8 +30,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked
@observable private _rowHeight: Opt<number>; // temporary store of row height to make change undoable
@observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll
+ private dropLocation: object = {}; // sets the drop location for external drops
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
@computed get numCols() { return NumCast(this.props.Document.gridNumCols, 10); }
@computed get rowHeight() { return this._rowHeight === undefined ? NumCast(this.props.Document.gridRowHeight, 100) : this._rowHeight; }
@@ -47,6 +48,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
@computed get flexGrid() { return BoolCast(this.props.Document.gridFlex, true); } // is grid static/flexible i.e. whether nodes be moved around and resized
@computed get compaction() { return StrCast(this.props.Document.gridStartCompaction, StrCast(this.props.Document.gridCompaction, "vertical")); } // is grid static/flexible i.e. whether nodes be moved around and resized
+ /**
+ * Sets up the listeners for the list of documents and the reset button.
+ */
componentDidMount() {
this._changeListenerDisposer = reaction(() => this.childLayoutPairs, (pairs) => {
const newLayouts: Layout[] = [];
@@ -54,7 +58,15 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
pairs.forEach((pair, i) => {
const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
if (existing) newLayouts.push(existing);
- else this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ else {
+ if (Object.keys(this.dropLocation).length) { // external drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number, y: number }, !this.flexGrid));
+ this.dropLocation = {};
+ }
+ else { // internal drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ }
+ }
});
pairs?.length && this.setLayoutList(newLayouts);
}, { fireImmediately: true });
@@ -68,11 +80,18 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
});
}
+ /**
+ * Disposes the listeners.
+ */
componentWillUnmount() {
this._changeListenerDisposer?.();
this._resetListenerDisposer?.();
}
+ /**
+ * @returns the default location of the grid node (i.e. when the grid is static)
+ * @param index
+ */
unflexedPosition(index: number): Omit<Layout, "i"> {
return {
x: (index % Math.floor(this.numCols / this.defaultW)) * this.defaultW,
@@ -83,6 +102,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
};
}
+ /**
+ * Maps the x- and y- coordinates of the event to a grid cell.
+ */
screenToCell(sx: number, sy: number) {
const pt = this.props.ScreenToLocalTransform().transformPoint(sx, sy);
const x = Math.floor(pt[0] / this.colWidthPlusGap);
@@ -90,10 +112,16 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
return { x, y };
}
+ /**
+ * Creates a layout object for a grid item
+ */
makeLayoutItem = (doc: Doc, pos: { x: number, y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
return ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
}
+ /**
+ * Adds a layout to the list of layouts.
+ */
addLayoutItem = (layouts: Layout[], layout: Layout) => {
const f = layouts.findIndex(l => l.i === layout.i);
f !== -1 && layouts.splice(f, 1);
@@ -215,6 +243,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
this.savedLayoutList.map((layout, index) => Object.assign(layout, this.unflexedPosition(index)));
}
+ /**
+ * Handles internal drop of Dash documents.
+ */
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
const savedLayouts = this.savedLayoutList;
@@ -228,12 +259,24 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
}
/**
+ * Handles external drop of images/PDFs etc from outside Dash.
+ */
+ @action
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ this.dropLocation = this.screenToCell(e.clientX, e.clientY);
+ super.onExternalDrop(e, {});
+ }
+
+ /**
* Handles the change in the value of the rowHeight slider.
*/
@action
onSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this._rowHeight = event.currentTarget.valueAsNumber;
}
+ /**
+ * Handles the user clicking on the slider.
+ */
@action
onSliderDown = (e: React.PointerEvent) => {
this._rowHeight = this.rowHeight; // uses _rowHeight during dragging and sets doc's rowHeight when finished so that operation is undoable
@@ -248,11 +291,13 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
*/
onContextMenu = () => {
const displayOptionsMenu: ContextMenuProps[] = [];
- displayOptionsMenu.push({ description: "Contents", event: () => this.props.Document.display = "contents", icon: "copy" });
- displayOptionsMenu.push({ description: "Undefined", event: () => this.props.Document.display = undefined, icon: "exclamation" });
+ displayOptionsMenu.push({ description: "Toggle Content Display Style", event: () => this.props.Document.display = this.props.Document.display ? undefined : "contents", icon: "copy" });
ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
}
+ /**
+ * Handles text document creation on double click.
+ */
onPointerDown = (e: React.PointerEvent) => {
if (this.props.active(true)) {
setupMoveUpEvents(this, e, returnFalse, returnFalse,
@@ -276,8 +321,11 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
<div className="collectionGridView-contents" ref={this.createDashEventsTarget}
style={{ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined }}
onContextMenu={this.onContextMenu}
- onPointerDown={e => this.onPointerDown(e)} >
+ onPointerDown={this.onPointerDown}
+ onDrop={this.onExternalDrop}
+ >
<div className="collectionGridView-gridContainer" ref={this._containerRef}
+ style={{ backgroundColor: StrCast(this.layoutDoc._backgroundColor, "white") }}
onWheel={e => e.stopPropagation()}
onScroll={action(e => {
if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index cd25c21b4..21d283547 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -202,9 +202,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
}
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); }
-
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 51dcdcfe6..d02088a6c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -202,8 +202,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
}
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); }
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index 406a38c26..d26b7920a 100644
--- a/src/client/views/linking/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
@@ -3,31 +3,60 @@
.linkEditor {
width: 100%;
height: auto;
- font-size: 12px; // TODO
+ font-size: 13px; // TODO
user-select: none;
}
.linkEditor-button-back {
- margin-bottom: 6px;
+ //margin-bottom: 6px;
border-radius: 10px;
width: 18px;
height: 18px;
padding: 0;
+
+ &:hover {
+ cursor: pointer;
+ }
}
.linkEditor-info {
//border-bottom: 0.5px solid $light-color-secondary;
- padding-bottom: 4px;
+ //padding-bottom: 1px;
padding-top: 5px;
padding-left: 5px;
//margin-bottom: 6px;
display: flex;
justify-content: space-between;
+ color: black;
.linkEditor-linkedTo {
width: calc(100% - 26px);
padding-left: 5px;
- padding-right: 5px
+ padding-right: 5px;
+
+ .linkEditor-downArrow {
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+}
+
+.linkEditor-moreInfo {
+ margin-left: 12px;
+ padding-left: 13px;
+ padding-right: 6.5px;
+ padding-bottom: 4px;
+ font-size: 9px;
+ //font-style: italic;
+ text-decoration-color: grey;
+
+ .button {
+ color: black;
+
+ &:hover {
+ cursor: pointer;
+ }
}
}
@@ -36,25 +65,58 @@
padding-right: 6.5px;
padding-bottom: 3.5px;
- .linkEditor-description-text {
- text-decoration-color: grey;
+ .linkEditor-description-label {
+ text-decoration-color: black;
+ color: black;
}
.linkEditor-description-input {
- border: 1px solid grey;
- border-radius: 4px;
- background-color: rgb(236, 236, 236);
- padding-left: 2px;
- padding-right: 2px;
- color: grey;
- text-decoration-color: grey;
+ display: flex;
+
+ .linkEditor-description-editing {
+ min-width: 85%;
+ //border: 1px solid grey;
+ //border-radius: 4px;
+ padding-left: 2px;
+ padding-right: 2px;
+ //margin-right: 4px;
+ color: black;
+ text-decoration-color: grey;
+ }
+
+ .linkEditor-description-add-button {
+ display: inline;
+ /* float: right; */
+ border-radius: 7px;
+ font-size: 9px;
+ background-color: black;
+ /* padding: 3px; */
+ padding-top: 4px;
+ padding-left: 7px;
+ padding-bottom: 4px;
+ padding-right: 8px;
+ height: 80%;
+ color: white;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
}
}
.linkEditor-followingDropdown {
padding-left: 6.5px;
padding-right: 6.5px;
- padding-bottom: 3.5px;
+ padding-bottom: 6px;
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .linkEditor-followingDropdown-label {
+ color: black;
+ }
.linkEditor-followingDropdown-dropdown {
@@ -62,14 +124,15 @@
border: 1px solid grey;
border-radius: 4px;
- background-color: rgb(236, 236, 236);
+ //background-color: rgb(236, 236, 236);
padding-left: 2px;
padding-right: 2px;
- color: grey;
- text-decoration-color: grey;
+ text-decoration-color: black;
+ color: rgb(94, 94, 94);
.linkEditor-followingDropdown-icon {
float: right;
+ color: black;
}
}
@@ -77,17 +140,22 @@
padding-left: 3px;
padding-right: 3px;
+ &:last-child {
+ border-bottom: none;
+ }
+
.linkEditor-followingDropdown-option {
- border: 0.25px dotted grey;
- background-color: rgb(236, 236, 236);
+ border: 0.25px solid grey;
+ //background-color: rgb(236, 236, 236);
padding-left: 2px;
padding-right: 2px;
color: grey;
text-decoration-color: grey;
font-size: 9px;
+ border-top: none;
&:hover {
- background-color: rgb(211, 210, 210);
+ background-color: rgb(187, 220, 231);
}
}
@@ -98,6 +166,10 @@
}
+
+
+
+
.linkEditor-button,
.linkEditor-addbutton {
width: 18px;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 014d57ed0..ed2bea722 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -1,10 +1,10 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faArrowLeft, faCog, faEllipsisV, faExchangeAlt, faPlus, faTable, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, computed } from "mobx";
+import { action, observable, computed, toJS } from "mobx";
import { observer } from "mobx-react";
import { Doc, Opt } from "../../../fields/Doc";
-import { StrCast } from "../../../fields/Types";
+import { StrCast, DateCast } from "../../../fields/Types";
import { Utils } from "../../../Utils";
import { LinkManager } from "../../util/LinkManager";
import './LinkEditor.scss';
@@ -13,6 +13,7 @@ import { DocumentView } from "../nodes/DocumentView";
import { DocumentLinksButton } from "../nodes/DocumentLinksButton";
import { EditableView } from "../EditableView";
import { RefObject } from "react";
+import { Tooltip } from "@material-ui/core";
library.add(faArrowLeft, faEllipsisV, faTable, faTrash, faCog, faExchangeAlt, faTimes, faPlus);
@@ -285,11 +286,11 @@ interface LinkEditorProps {
@observer
export class LinkEditor extends React.Component<LinkEditorProps> {
-
@observable description = StrCast(LinkManager.currentLink?.description);
@observable openDropdown: boolean = false;
-
- @observable followBehavior = this.props.linkDoc.follow ? this.props.linkDoc.follow : "Default";
+ @observable showInfo: boolean = false;
+ @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; }
+ @observable private buttonColor: string = "black";
//@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION";
@@ -304,23 +305,54 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
setDescripValue = (value: string) => {
if (LinkManager.currentLink) {
LinkManager.currentLink.description = value;
+ this.buttonColor = "rgb(62, 133, 55)";
+ setTimeout(action(() => {
+ this.buttonColor = "black";
+ }), 750);
return true;
}
}
+ @action
+ onKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ if (e.key === "Enter") {
+ this.setDescripValue(this.description);
+ document.getElementById('input')?.blur();
+ }
+ }
+
+ @action
+ onDown = () => {
+ this.setDescripValue(this.description);
+ }
+
+ @action
+ handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.description = e.target.value;
+ }
+
+
@computed
get editDescription() {
return <div className="linkEditor-description">
<div className="linkEditor-description-label">
- Link Description:</div>
+ Link Label:</div>
<div className="linkEditor-description-input">
- <EditableView
- GetValue={() => StrCast(LinkManager.currentLink?.description)}
- SetValue={value => { this.setDescripValue(value); return false; }}
- contents={LinkManager.currentLink?.description}
- placeholder={"(optional) enter link description"}
- color={"rgb(88, 88, 88)"}
- ></EditableView></div></div>;
+ <div className="linkEditor-description-editing">
+ <input
+ style={{ width: "100%" }}
+ id="input"
+ value={this.description}
+ placeholder={"enter link label"}
+ // color={"rgb(88, 88, 88)"}
+ onKeyDown={this.onKey}
+ onChange={this.handleChange}
+ ></input>
+ </div>
+ <div className="linkEditor-description-add-button"
+ style={{ backgroundColor: this.buttonColor }}
+ onPointerDown={this.onDown}>Set</div>
+ </div></div>;
}
@action
@@ -331,8 +363,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@action
changeFollowBehavior = (follow: string) => {
this.openDropdown = false;
- this.followBehavior = follow;
- this.props.linkDoc.follow = follow;
+ Doc.GetProto(this.props.linkDoc).followLinkLocation = follow;
}
@computed
@@ -343,10 +374,10 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor-followingDropdown-dropdown">
<div className="linkEditor-followingDropdown-header"
onPointerDown={this.changeDropdown}>
- {this.followBehavior}
+ {StrCast(this.props.linkDoc.followLinkLocation, "Default")}
<FontAwesomeIcon className="linkEditor-followingDropdown-icon"
icon={this.openDropdown ? "chevron-up" : "chevron-down"}
- size={"lg"} onPointerDown={this.changeDropdown} />
+ size={"lg"} />
</div>
<div className="linkEditor-followingDropdown-optionsList"
style={{ display: this.openDropdown ? "" : "none" }}>
@@ -355,11 +386,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
Default
</div>
<div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("Always open in right tab")}>
+ onPointerDown={() => this.changeFollowBehavior("onRight")}>
Always open in right tab
</div>
<div className="linkEditor-followingDropdown-option"
- onPointerDown={() => this.changeFollowBehavior("Always open in new tab")}>
+ onPointerDown={() => this.changeFollowBehavior("inTab")}>
Always open in new tab
</div>
</div>
@@ -367,6 +398,11 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
</div>;
}
+ @action
+ changeInfo = () => {
+ this.showInfo = !this.showInfo;
+ }
+
render() {
const destination = LinkManager.Instance.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc);
@@ -378,15 +414,25 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
return !destination ? (null) : (
<div className="linkEditor">
<div className="linkEditor-info">
- <button className="linkEditor-button-back"
- style={{ display: this.props.hideback ? "none" : "" }}
- onClick={this.props.showLinks}>
- <FontAwesomeIcon icon="arrow-left" size="sm" /> </button>
- <p className="linkEditor-linkedTo">editing link to: <b>{
+ <Tooltip title={<><div className="dash-tooltip">Return to link menu</div></>} placement="top">
+ <button className="linkEditor-button-back"
+ style={{ display: this.props.hideback ? "none" : "" }}
+ onClick={this.props.showLinks}>
+ <FontAwesomeIcon icon="arrow-left" size="sm" /> </button>
+ </Tooltip>
+ <p className="linkEditor-linkedTo">Editing Link to: <b>{
destination.proto?.title ?? destination.title ?? "untitled"}</b></p>
- <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
- <FontAwesomeIcon icon="trash" size="sm" /></button>
+ {/* <button className="linkEditor-button" onPointerDown={() => this.deleteLink()} title="Delete link">
+ <FontAwesomeIcon icon="trash" size="sm" /></button> */}
+ <Tooltip title={<><div className="dash-tooltip">Show more link information</div></>} placement="top">
+ <div className="linkEditor-downArrow"><FontAwesomeIcon className="button" icon={this.infoIcon} size="lg" onPointerDown={this.changeInfo} /></div>
+ </Tooltip>
</div>
+ {this.showInfo ? <div className="linkEditor-moreInfo">
+ <div>{this.props.linkDoc.author ? <div> <b>Author:</b> {this.props.linkDoc.author}</div> : null}</div>
+ <div>{this.props.linkDoc.creationDate ? <div> <b>Creation Date:</b>
+ {DateCast(this.props.linkDoc.creationDate).toString()}</div> : null}</div>
+ </div> : null}
<div>{this.editDescription}</div>
<div>{this.followingDropdown}</div>
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index 4b1a3f425..98e4171f0 100644
--- a/src/client/views/linking/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
@@ -1,33 +1,66 @@
@import "../globalCssVariables";
.linkMenu {
- width: 100%;
+ width: auto;
height: auto;
- //border: 1px solid black;
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ .linkMenu-list {
+
+ display: inline-block;
+
+ border: 1px solid black;
+
+ box-shadow: 3px 3px 1.5px grey;
+
+ max-height: 170px;
+ overflow-y: scroll;
+ position: relative;
+ z-index: 10;
+ background: white;
+ min-width: 170px;
+ //border-radius: 5px;
+ //padding-top: 6.5px;
+ //padding-bottom: 6.5px;
+ //padding-left: 6.5px;
+ //padding-right: 2px;
+ //width: calc(auto + 50px);
+
+ white-space: nowrap;
+ overflow-x: hidden;
+ width: 240px;
+ scrollbar-color: white;
+
+ &:last-child {
+ border-bottom: none;
+ }
- &:hover {
- width: calc(auto + 26px);
+ &:hover {
+ scrollbar-color: rgb(201, 239, 252);
+ }
}
-}
-.linkMenu-list {
- border: 1px solid black;
- max-height: 200px;
- overflow-y: scroll;
- position: absolute;
- z-index: 10;
- background: white;
- min-width: 150px;
- border-radius: 5px;
- padding-top: 6.5px;
- padding-bottom: 6.5px;
- padding-left: 6.5px;
- padding-right: 2px;
- //width: calc(auto + 50px);
+ .linkMenu-listEditor {
+
+ display: inline-block;
+
+ border: 1px solid black;
+
+ box-shadow: 3px 3px 1.5px grey;
+
+ max-height: 170px;
+ overflow-y: scroll;
+ position: relative;
+ z-index: 10;
+ background: white;
+ min-width: 170px;
+ }
}
.linkMenu-group {
- //border-bottom: 0.5px solid lightgray;
+ border-bottom: 0.5px solid lightgray;
//@extend: 5px 0;
@@ -36,7 +69,6 @@
}
.linkMenu-group-name {
- display: flex;
&:hover {
@@ -46,7 +78,7 @@
}
p.expand-one {
- width: calc(100% + 26px);
+ width: calc(100% + 20px);
}
.linkEditor-tableButton {
@@ -56,7 +88,7 @@
p {
width: 100%;
- padding: 4px 6px;
+ //padding: 4px 6px;
line-height: 12px;
border-radius: 5px;
font-weight: bold;
diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 8a7b12f48..2d151e9bc 100644
--- a/src/client/views/linking/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
import { observer } from "mobx-react";
import { DocumentView } from "../nodes/DocumentView";
import { LinkEditor } from "./LinkEditor";
@@ -29,14 +29,23 @@ export class LinkMenu extends React.Component<Props> {
@observable private _linkMenuRef = React.createRef<HTMLDivElement>();
private _editorRef = React.createRef<HTMLDivElement>();
+ //@observable private _numLinks: number = 0;
+
+ // @computed get overflow() {
+ // if (this._numLinks) {
+ // return "scroll";
+ // }
+ // return "auto";
+ // }
+
@action
onClick = (e: PointerEvent) => {
LinkDocPreview.LinkInfo = undefined;
- if (this._linkMenuRef && !!!this._linkMenuRef.current?.contains(e.target as any)) {
- if (this._editorRef && !!!this._editorRef.current?.contains(e.target as any)) {
+ if (this._linkMenuRef && !this._linkMenuRef.current?.contains(e.target as any)) {
+ if (this._editorRef && !this._editorRef.current?.contains(e.target as any)) {
console.log("outside click");
DocumentLinksButton.EditLink = undefined;
}
@@ -81,13 +90,18 @@ export class LinkMenu extends React.Component<Props> {
const sourceDoc = this.props.docView.props.Document;
const groups: Map<string, Doc[]> = LinkManager.Instance.getRelatedGroupedLinks(sourceDoc);
return <div className="linkMenu" ref={this._linkMenuRef} >
- <div className="linkMenu-list"
- style={{ left: this.props.location[0], top: this.props.location[1] }}>
- {!this._editingLink ?
- this.renderAllGroups(groups) :
+ {!this._editingLink ? <div className="linkMenu-list" style={{
+ left: this.props.location[0], top: this.props.location[1]
+ }}>
+ {this.renderAllGroups(groups)}
+ </div> : <div className="linkMenu-listEditor" style={{
+ left: this.props.location[0], top: this.props.location[1]
+ }}>
<LinkEditor sourceDoc={this.props.docView.props.Document} linkDoc={this._editingLink}
showLinks={action(() => this._editingLink = undefined)} />
- }
- </div> </div>;
+ </div>
+ }
+
+ </div>;
}
} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index ec17776e3..2ae87ac13 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -11,6 +11,7 @@ import { DocumentView } from "../nodes/DocumentView";
import './LinkMenu.scss';
import { LinkMenuItem, StartLinkTargetsDrag } from "./LinkMenuItem";
import React = require("react");
+import { Cast } from "../../../fields/Types";
interface LinkMenuGroupProps {
sourceDoc: Doc;
@@ -66,7 +67,8 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
render() {
const groupItems = this.props.group.map(linkDoc => {
- const destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
+ const destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc) ||
+ LinkManager.Instance.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null));
if (destination && this.props.sourceDoc) {
return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]}
groupType={this.props.groupType}
@@ -82,11 +84,13 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
return (
<div className="linkMenu-group" ref={this._menuRef}>
+
{/* <div className="linkMenu-group-name">
<p ref={this._drag} onPointerDown={this.onLinkButtonDown}
className={this.props.groupType === "*" || this.props.groupType === "" ? "" : "expand-one"} > {this.props.groupType}:</p>
{this.props.groupType === "*" || this.props.groupType === "" ? <></> : this.viewGroupAsTable(this.props.groupType)}
</div> */}
+
<div className="linkMenu-group-wrapper">
{groupItems}
</div>
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index 67bf71fb9..4e13ef8c8 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -4,21 +4,72 @@
// border-top: 0.5px solid $main-accent;
position: relative;
display: flex;
+ border-bottom: 0.5px solid black;
+
+ padding-left: 6.5px;
+ padding-right: 2px;
+
+ padding-top: 6.5px;
+ padding-bottom: 6.5px;
+
+ background-color: white;
.linkMenu-name {
position: relative;
+ width: auto;
.linkMenu-text {
padding: 4px 2px;
//display: inline;
- .linkMenu-destination-title {
+ .linkMenu-source-title {
text-decoration: none;
- color: rgb(85, 120, 196);
- font-size: 14px;
- padding-bottom: 2px;
+ color: rgb(43, 43, 43);
+ font-size: 7px;
+ padding-bottom: 0.75px;
+
+ margin-left: 20px;
+ }
+
+
+ .linkMenu-title-wrapper {
+
+ display: flex;
+
+ .destination-icon-wrapper {
+ //border: 0.5px solid rgb(161, 161, 161);
+ margin-right: 2px;
+ padding-right: 2px;
+
+ .destination-icon {
+ float: left;
+ width: 12px;
+ height: 12px;
+ padding: 1px;
+ margin-right: 3px;
+ color: rgb(161, 161, 161);
+ }
+ }
+
+ .linkMenu-destination-title {
+ text-decoration: none;
+ color: rgb(85, 120, 196);
+ font-size: 14px;
+ padding-bottom: 2px;
+ padding-right: 4px;
+ margin-right: 4px;
+ float: right;
+
+ &:hover {
+ text-decoration: underline;
+ color: rgb(60, 90, 156);
+ //display: inline;
+ text-overflow: break;
+ cursor: pointer;
+ }
+ }
}
.linkMenu-description {
@@ -26,15 +77,20 @@
font-style: italic;
color: rgb(95, 97, 102);
font-size: 10px;
+ margin-left: 20px;
+ max-width: 125px;
+ height: auto;
+ white-space: break-spaces;
}
p {
- //padding: 4px 2px;
+ padding-right: 4px;
line-height: 12px;
border-radius: 5px;
- overflow-wrap: break-word;
+ //overflow-wrap: break-word;
user-select: none;
}
+
}
}
@@ -53,6 +109,7 @@
&:hover {
+ background-color: rgb(201, 239, 252);
.linkMenu-item-buttons {
display: flex;
@@ -66,20 +123,6 @@
//display: inline;
text-overflow: break;
}
-
- &.expand-two p {
- width: calc(100% - 52px);
- //text-decoration: underline;
- //color: rgb(15, 57, 148);
- //background-color: lightgray;
- }
-
- &.expand-three p {
- width: calc(100% - 84px);
- //text-decoration: underline;
- //color: rgb(15, 57, 148);
- //background-color: lightgray;
- }
}
}
}
@@ -96,7 +139,8 @@
width: 20px;
height: 20px;
margin: 0;
- margin-right: 6px;
+ margin-right: 4px;
+ padding-right: 6px;
border-radius: 50%;
pointer-events: auto;
background-color: $dark-color;
@@ -119,7 +163,7 @@
&:hover {
background: $main-accent;
- cursor: grab;
+ cursor: pointer;
}
}
} \ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 6af474513..0e847632b 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes, faPencilAlt, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import { Doc, DocListCast } from '../../../fields/Doc';
@@ -15,7 +15,9 @@ import { setupMoveUpEvents, emptyFunction } from '../../../Utils';
import { DocumentView } from '../nodes/DocumentView';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
-library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt);
+import { Tooltip } from '@material-ui/core';
+import { DocumentType } from '../../documents/DocumentTypes';
+library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp, faPencilAlt, faEyeSlash);
interface LinkMenuItemProps {
@@ -78,7 +80,10 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
@action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; }
onEdit = (e: React.PointerEvent): void => {
+
+ console.log("Edit");
LinkManager.currentLink = this.props.linkDoc;
+ console.log(this.props.linkDoc);
setupMoveUpEvents(this, e, this.editMoved, emptyFunction, () => this.props.showEditor(this.props.linkDoc));
}
@@ -151,14 +156,8 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
DocumentLinksButton.EditLink = undefined;
LinkDocPreview.LinkInfo = undefined;
- if (this.props.linkDoc.follow) {
- if (this.props.linkDoc.follow === "Default") {
- DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
- } else if (this.props.linkDoc.follow === "Always open in right tab") {
- this.props.addDocTab(this.props.destinationDoc, "onRight");
- } else if (this.props.linkDoc.follow === "Always open in new tab") {
- this.props.addDocTab(this.props.destinationDoc, "inTab");
- }
+ if (this.props.linkDoc.followLinkLocation && this.props.linkDoc.followLinkLocation !== "Default") {
+ this.props.addDocTab(this.props.destinationDoc, StrCast(this.props.linkDoc.followLinkLocation));
} else {
DocumentManager.Instance.FollowLink(this.props.linkDoc, this.props.sourceDoc, doc => this.props.addDocTab(doc, "onRight"), false);
}
@@ -172,13 +171,53 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
DocumentLinksButton.EditLink = undefined;
}
+ @action
+ showLink = () => {
+ this.props.linkDoc.hidden = !this.props.linkDoc.hidden;
+ }
+
render() {
const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
const canExpand = keys ? keys.length > 0 : false;
+ const eyeIcon = this.props.linkDoc.hidden ? "eye-slash" : "eye";
+
+ let destinationIcon: FontAwesomeIconProps["icon"] = "question";
+ switch (this.props.destinationDoc.type) {
+ case DocumentType.IMG: destinationIcon = "image"; break;
+ case DocumentType.COMPARISON: destinationIcon = "columns"; break;
+ case DocumentType.RTF: destinationIcon = "font"; break;
+ case DocumentType.COL: destinationIcon = "folder"; break;
+ case DocumentType.WEB: destinationIcon = "globe-asia"; break;
+ case DocumentType.SCREENSHOT: destinationIcon = "photo-video"; break;
+ case DocumentType.WEBCAM: destinationIcon = "video"; break;
+ case DocumentType.AUDIO: destinationIcon = "microphone"; break;
+ case DocumentType.BUTTON: destinationIcon = "bolt"; break;
+ case DocumentType.PRES: destinationIcon = "tv"; break;
+ case DocumentType.QUERY: destinationIcon = "search"; break;
+ case DocumentType.SCRIPTING: destinationIcon = "terminal"; break;
+ case DocumentType.IMPORT: destinationIcon = "cloud-upload-alt"; break;
+ case DocumentType.DOCHOLDER: destinationIcon = "expand"; break;
+ case DocumentType.VID: destinationIcon = "video"; break;
+ case DocumentType.INK: destinationIcon = "pen-nib"; break;
+ default: destinationIcon = "question"; break;
+ }
+
+ const title = StrCast(this.props.destinationDoc.title).length > 18 ?
+ StrCast(this.props.destinationDoc.title).substr(0, 14) + "..." : this.props.destinationDoc.title;
+
+ // ...
+ // from anika to bob: here's where the text that is specifically linked would show up (linkDoc.storedText)
+ // ...
+ const source = this.props.sourceDoc.type === DocumentType.RTF ? this.props.linkDoc.storedText ?
+ StrCast(this.props.linkDoc.storedText).length > 17 ?
+ StrCast(this.props.linkDoc.storedText).substr(0, 18)
+ : this.props.linkDoc.storedText : undefined : undefined;
+
return (
<div className="linkMenu-item">
<div className={canExpand ? "linkMenu-item-content expand-three" : "linkMenu-item-content expand-two"}>
+
<div ref={this._drag} className="linkMenu-name" //title="drag to view target. click to customize."
onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
onPointerEnter={action(e => this.props.linkDoc && (LinkDocPreview.LinkInfo = {
@@ -190,9 +229,16 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
onPointerDown={this.onLinkButtonDown}>
<div className="linkMenu-text">
- <p className="linkMenu-destination-title"
- onPointerDown={this.followDefault}>
- {StrCast(this.props.destinationDoc.title)}</p>
+ {source ? <p className="linkMenu-source-title">
+ <b>Source: {source}</b></p> : null}
+ <div className="linkMenu-title-wrapper">
+ <div className="destination-icon-wrapper" >
+ <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" /></div>
+ <p className="linkMenu-destination-title"
+ onPointerDown={this.followDefault}>
+ {title}
+ </p>
+ </div>
{this.props.linkDoc.description !== "" ? <p className="linkMenu-description">
{StrCast(this.props.linkDoc.description)}</p> : null} </div>
@@ -200,10 +246,19 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
{canExpand ? <div title="Show more" className="button" onPointerDown={e => this.toggleShowMore(e)}>
<FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
- <div title="Edit link" className="button" ref={this._editRef} onPointerDown={this.onEdit}>
- <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
- <div title="Delete link" className="button" onPointerDown={this.deleteLink}>
- <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show link" : "Hide link"}</div></>}>
+ <div className="button" ref={this._editRef} onPointerDown={this.showLink}>
+ <FontAwesomeIcon className="fa-icon" icon={eyeIcon} size="sm" /></div>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}>
+ <div className="button" ref={this._editRef} onPointerDown={this.onEdit}>
+ <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
+ </Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>}>
+ <div className="button" onPointerDown={this.deleteLink}>
+ <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
+ </Tooltip>
{/* <div title="Follow link" className="button" onPointerDown={this.followDefault} onContextMenu={this.onContextMenu}>
<FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div> */}
</div>
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index c959b79f5..e9420a072 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -5,6 +5,7 @@
position: inherit;
display: flex;
pointer-events: all;
+ position: relative;
cursor: default;
.audiobox-buttons {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index d5288fff6..5c921cea4 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -268,6 +268,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
LayoutTemplate={undefined}
LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(l, la2)}`)}
ContainingCollectionDoc={this.props.Document}
+ dontRegisterView={true}
parentActive={returnTrue}
bringToFront={emptyFunction}
backgroundColor={returnTransparent} />
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index f797ffe8a..b975e24e2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -76,9 +76,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
public static getValues(doc: Doc, time: number) {
const timecode = Math.round(time);
return ({
- x: Cast(doc["x-indexed"], listSpec("number"), []).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number),
- y: Cast(doc["y-indexed"], listSpec("number"), []).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number),
- opacity: Cast(doc["opacity-indexed"], listSpec("number"), []).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number),
+ x: Cast(doc["x-indexed"], listSpec("number"), [NumCast(doc.x)]).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number),
+ y: Cast(doc["y-indexed"], listSpec("number"), [NumCast(doc.y)]).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number),
+ opacity: Cast(doc["opacity-indexed"], listSpec("number"), [NumCast(doc.opacity, 1)]).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number),
});
}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index b3a9b6fee..57028b0ca 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -14,7 +14,7 @@ import { ViewBoxBaseComponent } from "../DocComponent";
import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox } from "../InkingStroke";
import "./ColorBox.scss";
import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBox } from "./formattedText/FormattedTextBox";
+import { DocumentType } from "../../documents/DocumentTypes";
type ColorDocument = makeInterface<[typeof documentSchema]>;
const ColorDocument = makeInterface(documentSchema);
@@ -63,9 +63,15 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
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>) => SetActiveInkWidth(e.target.value)} />
+ <input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ SetActiveInkWidth(e.target.value);
+ SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(e.target.value));
+ }} />
<div> {ActiveInkBezierApprox() ?? 2}</div>
- <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveBezierApprox(e.target.value)} />
+ <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ SetActiveBezierApprox(e.target.value);
+ SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeBezier = e.target.value);
+ }} />
<br />
<br />
</div>
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index ba075886b..6081def5d 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -2,7 +2,6 @@ import React = require("react");
import { computed } from "mobx";
import { observer } from "mobx-react";
import { Transform } from "nodemailer/lib/xoauth2";
-import "react-table/react-table.css";
import { Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
@@ -10,7 +9,6 @@ import { TraceMobx } from "../../../fields/util";
import { emptyFunction } from "../../../Utils";
import { dropActionType } from "../../util/DragManager";
import { CollectionView } from "../collections/CollectionView";
-import '../DocumentDecorations.scss';
import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import "./ContentFittingDocumentView.scss";
diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss
index b58603978..97e714cd5 100644
--- a/src/client/views/nodes/DocumentLinksButton.scss
+++ b/src/client/views/nodes/DocumentLinksButton.scss
@@ -13,7 +13,7 @@
color: black;
text-transform: uppercase;
letter-spacing: 2px;
- font-size: 75%;
+ font-size: 10px;
transition: transform 0.2s;
text-align: center;
display: flex;
@@ -21,17 +21,26 @@
align-items: center;
&:hover {
- background: deepskyblue;
- transform: scale(1.05);
- cursor: grab;
+ // background: deepskyblue;
+ // transform: scale(1.05);
+ cursor: pointer;
}
}
+
.documentLinksButton {
background-color: $link-color;
}
+
.documentLinksButton-endLink {
border: red solid 2px;
+
+ &:hover {
+ background: deepskyblue;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
}
+
.documentLinksButton-startLink {
border: red solid 2px;
background-color: rgba(255, 192, 203, 0.5);
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 7fb447cab..bbef48e44 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../fields/Doc";
import { emptyFunction, setupMoveUpEvents, returnFalse } from "../../../Utils";
import { DragManager } from "../../util/DragManager";
-import { UndoManager } from "../../util/UndoManager";
+import { UndoManager, undoBatch } from "../../util/UndoManager";
import './DocumentLinksButton.scss';
import { DocumentView } from "./DocumentView";
import React = require("react");
@@ -13,6 +13,7 @@ import { LinkDocPreview } from "./LinkDocPreview";
import { LinkCreatedBox } from "./LinkCreatedBox";
import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
import { LinkManager } from "../../util/LinkManager";
+import { Tooltip } from "@material-ui/core";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -22,6 +23,7 @@ interface DocumentLinksButtonProps {
Offset?: number[];
AlwaysOn?: boolean;
InMenu?: boolean;
+ StartLink?: boolean;
}
@observer
export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -29,61 +31,66 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
@observable public static StartLink: DocumentView | undefined;
- @action
+ @action @undoBatch
onLinkButtonMoved = (e: PointerEvent) => {
- if (this._linkButton.current !== null) {
- const linkDrag = UndoManager.StartBatch("Drag Link");
- this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, {
- dragComplete: dropEv => {
- const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
- if (this.props.View && linkDoc) {
- !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
-
- // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
- // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
- // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView
- // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
- dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
- runInAction(() => this.props.View._link = linkDoc);
- setTimeout(action(() => this.props.View._link = undefined), 0);
- }
- linkDrag?.end();
- },
- hideSource: false
- });
- return true;
+ if (this.props.InMenu && this.props.StartLink) {
+ if (this._linkButton.current !== null) {
+ const linkDrag = UndoManager.StartBatch("Drag Link");
+ this.props.View && DragManager.StartLinkDrag(this._linkButton.current, this.props.View.props.Document, e.pageX, e.pageY, {
+ dragComplete: dropEv => {
+ const linkDoc = dropEv.linkDragData?.linkDocument as Doc; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop
+ if (this.props.View && linkDoc) {
+ !linkDoc.linkRelationship && (Doc.GetProto(linkDoc).linkRelationship = "hyperlink");
+
+ // we want to allow specific views to handle the link creation in their own way (e.g., rich text makes text hyperlinks)
+ // the dragged view can regiser a linkDropCallback to be notified that the link was made and to update their data structures
+ // however, the dropped document isn't so accessible. What we do is set the newly created link document on the documentView
+ // The documentView passes a function prop returning this link doc to its descendants who can react to changes to it.
+ dropEv.linkDragData?.linkDropCallback?.(dropEv.linkDragData);
+ runInAction(() => this.props.View._link = linkDoc);
+ setTimeout(action(() => this.props.View._link = undefined), 0);
+ }
+ linkDrag?.end();
+ },
+ hideSource: false
+ });
+ return true;
+ }
+ return false;
}
return false;
}
-
+ @undoBatch
onLinkButtonDown = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => {
- if (doubleTap && this.props.InMenu) {
+ if (doubleTap && this.props.InMenu && this.props.StartLink) {
//action(() => Doc.BrushDoc(this.props.View.Document));
DocumentLinksButton.StartLink = this.props.View;
- } else if (!!!this.props.InMenu) {
+ } else if (!this.props.InMenu) {
+ console.log("editing");
+ this.props.View ? console.log("view") : null;
DocumentLinksButton.EditLink = this.props.View;
DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
}
}));
}
- @action
+ @action @undoBatch
onLinkClick = (e: React.MouseEvent): void => {
- if (this.props.InMenu) {
+ if (this.props.InMenu && this.props.StartLink) {
DocumentLinksButton.StartLink = this.props.View;
//action(() => Doc.BrushDoc(this.props.View.Document));
- } else if (!!!this.props.InMenu) {
+ } else if (!this.props.InMenu) {
DocumentLinksButton.EditLink = this.props.View;
DocumentLinksButton.EditLinkLoc = [e.clientX + 10, e.clientY];
}
}
- @action
+ @action @undoBatch
completeLink = (e: React.PointerEvent): void => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e, doubleTap) => {
- if (doubleTap) {
+ if (doubleTap && this.props.InMenu && !!!this.props.StartLink) {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
// action((e: React.PointerEvent<HTMLDivElement>) => {
@@ -94,16 +101,20 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
LinkManager.currentLink = linkDoc;
+
runInAction(() => {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 133;
- LinkCreatedBox.linkCreated = true;
+ if (linkDoc) {
+ LinkCreatedBox.popupX = e.screenX;
+ LinkCreatedBox.popupY = e.screenY - 133;
+ LinkCreatedBox.linkCreated = true;
- LinkDescriptionPopup.popupX = e.screenX;
- LinkDescriptionPopup.popupY = e.screenY - 100;
- LinkDescriptionPopup.descriptionPopup = true;
+ LinkDescriptionPopup.popupX = e.screenX;
+ LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
+
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ }
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
});
}
}
@@ -111,7 +122,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
}));
}
- @action
+ @action @undoBatch
finishLinkClick = (e: React.MouseEvent) => {
if (DocumentLinksButton.StartLink === this.props.View) {
DocumentLinksButton.StartLink = undefined;
@@ -119,22 +130,27 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
// Doc.UnBrushDoc(this.props.View.Document);
// });
} else {
- if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
- const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
- LinkManager.currentLink = linkDoc;
- runInAction(() => {
- LinkCreatedBox.popupX = e.screenX;
- LinkCreatedBox.popupY = e.screenY - 133;
- LinkCreatedBox.linkCreated = true;
-
- if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
- LinkDescriptionPopup.popupX = e.screenX;
- LinkDescriptionPopup.popupY = e.screenY - 100;
- LinkDescriptionPopup.descriptionPopup = true;
- }
+ if (this.props.InMenu && !!!this.props.StartLink) {
+ if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View) {
+ const linkDoc = DocUtils.MakeLink({ doc: DocumentLinksButton.StartLink.props.Document }, { doc: this.props.View.props.Document }, "long drag");
+ LinkManager.currentLink = linkDoc;
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+ runInAction(() => {
+ if (linkDoc) {
+ LinkCreatedBox.popupX = e.screenX;
+ LinkCreatedBox.popupY = e.screenY - 133;
+ LinkCreatedBox.linkCreated = true;
+
+ if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) {
+ LinkDescriptionPopup.popupX = e.screenX;
+ LinkDescriptionPopup.popupY = e.screenY - 100;
+ LinkDescriptionPopup.descriptionPopup = true;
+ }
+
+ setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
+ }
+ });
+ }
}
}
}
@@ -147,32 +163,62 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
get linkButton() {
const links = DocListCast(this.props.View.props.Document.links);
- const title = this.props.InMenu ? "Drag or tap to create links" : "Tap to view links";
-
- return (!links.length || links[0].hidden) && !this.props.AlwaysOn ? (null) :
- <div title={title} ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
- <div className={"documentLinksButton"} style={{
- backgroundColor: DocumentLinksButton.StartLink ? "transparent" : this.props.InMenu ? "black" : "",
- color: this.props.InMenu ? "white" : "black",
- width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold"
- }}
- onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
- // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
- // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = {
- // addDocTab: this.props.View.props.addDocTab,
- // linkSrc: this.props.View.props.Document,
- // linkDoc: links[0],
- // Location: [e.clientX, e.clientY + 20]
- // }))}
- >
- {links.length && !!!this.props.InMenu ? links.length : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
- </div>
- {DocumentLinksButton.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)}
- {DocumentLinksButton.StartLink === this.props.View ? <div className={"documentLinksButton-startLink"}
- style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)}
- </div>;
+ const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link";
+ const buttonTitle = "Tap to view links";
+ const title = this.props.InMenu ? menuTitle : buttonTitle;
+
+
+ const startLink = <img
+ style={{ width: "11px", height: "11px" }}
+ id={"startLink-icon"}
+ src={`/assets/${"startLink.png"}`} />;
+
+ const endLink = <img
+ style={{ width: "14px", height: "9px" }}
+ id={"endLink-icon"}
+ src={`/assets/${"endLink.png"}`} />;
+
+ const link = <img
+ style={{ width: "22px", height: "16px" }}
+ id={"link-icon"}
+ src={`/assets/${"link.png"}`} />;
+
+ const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}>
+ <div className={"documentLinksButton"} style={{
+ backgroundColor: this.props.InMenu ? "black" : "",
+ color: this.props.InMenu ? "white" : "black",
+ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold"
+ }}
+ onPointerDown={this.onLinkButtonDown} onClick={this.onLinkClick}
+ // onPointerLeave={action(() => LinkDocPreview.LinkInfo = undefined)}
+ // onPointerEnter={action(e => links.length && (LinkDocPreview.LinkInfo = {
+ // addDocTab: this.props.View.props.addDocTab,
+ // linkSrc: this.props.View.props.Document,
+ // linkDoc: links[0],
+ // Location: [e.clientX, e.clientY + 20]
+ // }))}
+ >
+
+ {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> :
+ <FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length}
+
+ </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)}
+ {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>;
+
+ return (!links.length) && !this.props.AlwaysOn ? (null) :
+ this.props.InMenu ?
+ <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
+ {linkButton}
+ </Tooltip> : !!!DocumentLinksButton.EditLink ?
+ <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
+ {linkButton}
+ </Tooltip> :
+ linkButton;
}
render() {
return this.linkButton;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 97e3bc01c..c0a8b3a59 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -11,7 +11,7 @@ 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 } from "../../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { emptyFunction, OmitKeys, returnOne, returnTransparent, Utils, emptyPath } from "../../../Utils";
@@ -68,10 +68,10 @@ export interface DocumentViewProps {
ignoreAutoHeight?: boolean;
contextMenuItems?: () => { script: ScriptField, label: string }[];
rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
- onClick?: ScriptField;
- onDoubleClick?: ScriptField;
- onPointerDown?: ScriptField;
- onPointerUp?: ScriptField;
+ onClick?: () => ScriptField;
+ onDoubleClick?: () => ScriptField;
+ onPointerDown?: () => ScriptField;
+ onPointerUp?: () => ScriptField;
treeViewDoc?: Doc;
dropAction?: dropActionType;
dragDivName?: string;
@@ -127,12 +127,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get freezeDimensions() { return this.props.FreezeDimensions; }
@computed get nativeWidth() { return NumCast(this.layoutDoc._nativeWidth, this.props.NativeWidth() || (this.freezeDimensions ? this.layoutDoc[WidthSym]() : 0)); }
@computed get nativeHeight() { return NumCast(this.layoutDoc._nativeHeight, this.props.NativeHeight() || (this.freezeDimensions ? this.layoutDoc[HeightSym]() : 0)); }
- @computed get onClickHandler() { return this.props.onClick || Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
- @computed get onDoubleClickHandler() { return this.props.onDoubleClick || Cast(this.layoutDoc.onDoubleClick, ScriptField, null) || this.Document.onDoubleClick; }
- @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
- @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
+ @computed get onClickHandler() { return this.props.onClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null)); }
+ @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); }
+ @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
+ @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
NativeWidth = () => this.nativeWidth;
NativeHeight = () => this.nativeHeight;
+ onClickFunc = () => this.onClickHandler;
+ onDoubleClickFunc = () => this.onDoubleClickHandler;
private _firstX: number = -1;
private _firstY: number = -1;
@@ -587,16 +589,12 @@ 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;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = false);
- this.Document.ignoreClick = false;
this.Document.onClick = this.layoutDoc.onClick = undefined;
} else {
this.Document.isLinkButton = true;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
this.Document.followLinkZoom = false;
this.Document.followLinkLocation = undefined;
}
@@ -604,14 +602,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
toggleFollowInPlace = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document.isLinkButton = !this.Document.isLinkButton;
if (this.Document.isLinkButton) {
- this.Document.isLinkButton = false;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = false);
- } else {
- this.Document.isLinkButton = true;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
this.Document.followLinkZoom = true;
this.Document.followLinkLocation = "inPlace";
}
@@ -619,15 +612,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
toggleFollowOnRight = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document.isLinkButton = !this.Document.isLinkButton;
if (this.Document.isLinkButton) {
- this.Document.isLinkButton = false;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = false);
- } else {
- this.Document.isLinkButton = true;
this.Document.followLinkZoom = false;
- const first = DocListCast(this.Document.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
this.Document.followLinkLocation = "onRight";
}
}
@@ -639,49 +627,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
alert("linking to document tabs not yet supported. Drop link on document content.");
return;
}
+ const makeLink = action((linkDoc: Doc) => {
+ LinkManager.currentLink = linkDoc;
+
+ LinkCreatedBox.popupX = de.x;
+ LinkCreatedBox.popupY = de.y - 33;
+ LinkCreatedBox.linkCreated = true;
+
+ LinkDescriptionPopup.popupX = de.x;
+ LinkDescriptionPopup.popupY = de.y;
+ LinkDescriptionPopup.descriptionPopup = true;
+
+ setTimeout(action(() => LinkCreatedBox.linkCreated = false), 2500);
+ });
if (de.complete.annoDragData) {
/// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner
e.stopPropagation();
de.complete.annoDragData.linkedToDoc = true;
const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link");
- LinkManager.currentLink = linkDoc;
-
- runInAction(() => {
- LinkCreatedBox.popupX = de.x;
- LinkCreatedBox.popupY = de.y - 33;
- LinkCreatedBox.linkCreated = true;
-
- LinkDescriptionPopup.popupX = de.x;
- LinkDescriptionPopup.popupY = de.y;
- LinkDescriptionPopup.descriptionPopup = true;
-
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+ linkDoc && makeLink(linkDoc);
}
if (de.complete.linkDragData) {
e.stopPropagation();
- // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
- // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
-
if (de.complete.linkDragData.linkSourceDocument !== this.props.Document) {
const linkDoc = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument },
{ doc: this.props.Document }, `link`);
- LinkManager.currentLink = linkDoc;
-
de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
(de.complete.linkDragData.linkDocument = linkDoc); // TODODO this is where in text links get passed
- runInAction(() => {
- LinkCreatedBox.popupX = de.x;
- LinkCreatedBox.popupY = de.y - 33;
- LinkCreatedBox.linkCreated = true;
-
- LinkDescriptionPopup.popupX = de.x;
- LinkDescriptionPopup.popupY = de.y;
- LinkDescriptionPopup.descriptionPopup = true;
-
- setTimeout(action(() => { LinkCreatedBox.linkCreated = false; }), 2500);
- });
+ linkDoc && makeLink(linkDoc);
}
}
@@ -782,10 +756,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const options = cm.findByDescription("Options...");
const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+ optionItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
templateDoc && optionItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "onRight"), icon: "eye" });
- optionItems.push({ description: "Toggle Show Each Link Dot", event: () => this.layoutDoc.showLinks = !this.layoutDoc.showLinks, icon: "eye" });
!options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+
const existingOnClick = cm.findByDescription("OnClick...");
const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
@@ -795,14 +770,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "edit" });
- !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
const funcs: ContextMenuProps[] = [];
if (this.layoutDoc.onDragStart) {
funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
- cm.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+ cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
}
const more = cm.findByDescription("More...");
@@ -829,9 +804,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// a.download = `DocExport-${this.props.Document[Id]}.zip`;
// a.click();
});
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
}
- moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "fingerprint" });
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" });
@@ -839,19 +813,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
const help = cm.findByDescription("Help...");
const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
- helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("http://localhost:1050/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" });
- cm.addItem({ description: "Help...", 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("addOnly"), icon: "concierge-bell" });
- aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
- aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
- aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
- aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
- aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
- !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
+ //!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" });
+ 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("addOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Read Only", event: () => this.setAcl("readOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Private", event: () => this.setAcl("ownerOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Make Editable", event: () => this.setAcl("write"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Test Private", event: () => this.testAcl("ownerOnly"), icon: "concierge-bell" });
+ // aclItems.push({ description: "Test Readonly", event: () => this.testAcl("readOnly"), icon: "concierge-bell" });
+ // !existingAcls && cm.addItem({ description: "Privacy...", subitems: aclItems, icon: "question" });
// const recommender_subitems: ContextMenuProps[] = [];
@@ -1082,10 +1056,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ChromeHeight={this.chromeHeight}
isSelected={this.isSelected}
select={this.select}
- onClick={this.onClickHandler}
+ onClick={this.onClickFunc}
layoutKey={this.finalLayoutKey} />
- {this.layoutDoc.showLinks ? this.anchors : (null)}
- {this.props.forcedBackgroundColor?.(this.Document) === "transparent" || this.props.dontRegisterView ? (null) : <DocumentLinksButton View={this} Offset={[-15, 0]} />}
+ {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]} />}
</div>
);
}
@@ -1107,12 +1082,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && (doc.hidden = true), true)
anchorPanelWidth = () => this.props.PanelWidth() || 1;
anchorPanelHeight = () => this.props.PanelHeight() || 1;
- @computed get anchors() {
+
+ @computed 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.props.dontRegisterView ? (null) : // view that are not registered
- DocUtils.FilterDocs(DocListCast(this.Document.links), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
+ DocUtils.FilterDocs(LinkManager.Instance.getAllDirectLinks(this.Document), this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink).map((d, i) =>
<DocumentView {...this.props} key={i + 1}
Document={d}
ContainingCollectionView={this.props.ContainingCollectionView}
@@ -1130,10 +1107,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@computed get innards() {
TraceMobx();
- if (this.props.treeViewDoc && !this.props.LayoutTemplateString) { // this happens when the document is a tree view label (but not an anchor dot)
+ if (this.props.treeViewDoc && !this.props.LayoutTemplateString?.includes("LinkAnchorBox")) { // this happens when the document is a tree view label (but not an anchor dot)
return <div className="documentView-treeView" style={{ maxWidth: this.props.PanelWidth() || undefined }}>
{StrCast(this.props.Document.title)}
- {this.anchors}
+ {this.allAnchors}
</div>;
}
@@ -1150,7 +1127,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ChromeHeight={this.chromeHeight}
isSelected={this.isSelected}
select={this.select}
- onClick={this.onClickHandler}
+ onClick={this.onClickFunc}
layoutKey={this.finalLayoutKey} />
</div>);
const titleView = (!showTitle ? (null) :
@@ -1256,7 +1233,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
background: finalColor,
opacity: finalOpacity,
fontFamily: StrCast(this.Document._fontFamily, "inherit"),
- fontSize: Cast(this.Document._fontSize, "number", null)
+ fontSize: Cast(this.Document._fontSize, "string", null)
}}>
{this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <>
{this.innards}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b1132ce33..5d5bc1d73 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -25,7 +25,7 @@ export interface FieldViewProps {
Document: Doc;
DataDoc?: Doc;
LibraryPath: Doc[];
- onClick?: ScriptField;
+ onClick?: () => ScriptField;
dropAction: dropActionType;
backgroundHalo?: () => boolean;
docFilters: () => string[];
diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss
index 68b00a5be..fe0f067ad 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -18,6 +18,7 @@
text-align: center;
font-size: 8px;
margin-top:4px;
+ letter-spacing: normal;
}
svg {
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index 5e8dd2497..86e9a4527 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>(
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" />
- {!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 5)} </div>}
+ {!this.rootDoc.label ? (null) : <div className="fontIconBox-label"> {StrCast(this.rootDoc.label).substring(0, 6)} </div>}
</button>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index d16aa528c..4eba21eab 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -157,29 +157,31 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
const funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
- funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
- funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
- // funcs.push({
- // description: "Reset Native Dimensions", event: action(async () => {
- // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
- // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
- // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
- // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
- // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
- // } else {
- // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
- // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
- // }
- // }), icon: "expand-arrows-alt"
- // });
-
- const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
- const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
- modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
- modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
- //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
- !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
+ funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" });
+ if (!Doc.UserDoc().noviceMode) {
+ funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
+ funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
+ // funcs.push({
+ // description: "Reset Native Dimensions", event: action(async () => {
+ // const curNW = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]);
+ // const curNH = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]);
+ // if (this.props.PanelWidth() / this.props.PanelHeight() > curNW / curNH) {
+ // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelHeight() * curNW / curNH;
+ // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelHeight();
+ // } else {
+ // this.dataDoc[this.fieldKey + "-nativeWidth"] = this.props.PanelWidth();
+ // this.dataDoc[this.fieldKey + "-nativeHeight"] = this.props.PanelWidth() * curNH / curNW;
+ // }
+ // }), icon: "expand-arrows-alt"
+ // });
+
+ const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers...");
+ const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
+ modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
+ modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
+ //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
+ !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
+ }
ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 360ead18e..0dfbdc5cf 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -41,7 +41,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
}, icon: "trash"
});
- ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
+ ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: funcs, icon: "asterisk" });
}
@undoBatch
@@ -67,7 +67,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument
<div className="labelBox-mainButton" style={{
background: StrCast(this.layoutDoc.backgroundColor),
color: StrCast(this.layoutDoc.color, "inherit"),
- fontSize: NumCast(this.layoutDoc._fontSize) || "inherit",
+ fontSize: StrCast(this.layoutDoc._fontSize) || "inherit",
fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit",
letterSpacing: StrCast(this.layoutDoc.letterSpacing),
textTransform: StrCast(this.layoutDoc.textTransform) as any,
diff --git a/src/client/views/nodes/LinkDescriptionPopup.scss b/src/client/views/nodes/LinkDescriptionPopup.scss
index 54002fd1b..d92823ccc 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.scss
+++ b/src/client/views/nodes/LinkDescriptionPopup.scss
@@ -48,6 +48,10 @@
position: relative;
margin-right: 4px;
justify-content: center;
+
+ &:hover{
+ cursor: pointer;
+ }
}
.linkDescriptionPopup-btn-add {
@@ -62,6 +66,10 @@
text-align: center;
position: relative;
justify-content: center;
+
+ &:hover{
+ cursor: pointer;
+ }
}
}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 3bb52d9fb..06e8d30d1 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -19,15 +19,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
@action
descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.description = e.currentTarget.value;
- }
-
- @action
- setDescription = () => {
- if (LinkManager.currentLink) {
- LinkManager.currentLink.description = this.description;
- }
- LinkDescriptionPopup.descriptionPopup = false;
+ LinkManager.currentLink && (LinkManager.currentLink.description = e.currentTarget.value);
}
@action
@@ -58,7 +50,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
}}>
- <input className="linkDescriptionPopup-input"
+ <input className="linkDescriptionPopup-input" onKeyPress={e => e.key === "Enter" && this.onDismiss()}
placeholder={"(optional) enter link label..."}
onChange={(e) => this.descriptionChanged(e)}>
</input>
@@ -66,7 +58,7 @@ export class LinkDescriptionPopup extends React.Component<{}> {
<div className="linkDescriptionPopup-btn-dismiss"
onPointerDown={this.onDismiss}> Dismiss </div>
<div className="linkDescriptionPopup-btn-add"
- onPointerDown={this.setDescription}> Add </div>
+ onPointerDown={this.onDismiss}> Add </div>
</div>
</div>;
}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 197dc8df4..1caa82380 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -98,23 +98,11 @@ export class LinkDocPreview extends React.Component<Props> {
{this._toolTipText}
</div>
</div> :
- // <div style={{
- // border: "6px solid white",
- // }}>
- // <div style={{ backgroundColor: "white" }}> {this._targetDoc.title}
- // <div className="wrapper" style={{ float: "right" }}>
- // <div title="Delete link" className="button" style={{ display: "inline" }} ref={this._editRef} onPointerDown={this.deleteLink}>
- // <FontAwesomeIcon className="fa-icon" icon="trash" size="sm" /></div>
- // <div title="Follow link" className="button" style={{ display: "inline" }} onClick={this.followDefault} onContextMenu={this.onContextMenu}>
- // <FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" />
- // </div>
- // </div>
- // </div>
+
<ContentFittingDocumentView
Document={this._targetDoc}
LibraryPath={emptyPath}
fitToBox={true}
- backgroundColor={this.props.backgroundColor}
moveDocument={returnFalse}
rootSelected={returnFalse}
ScreenToLocalTransform={Transform.Identity}
@@ -128,16 +116,15 @@ export class LinkDocPreview extends React.Component<Props> {
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
renderDepth={0}
- PanelWidth={this.width}
- PanelHeight={this.height}
+ PanelWidth={() => this.width() - 16} //Math.min(350, NumCast(target._width, 350))}
+ PanelHeight={() => this.height() - 16} //Math.min(250, NumCast(target._height, 250))}
focus={emptyFunction}
whenActiveChanged={returnFalse}
bringToFront={returnFalse}
ContentScaling={returnOne}
NativeWidth={returnZero}
NativeHeight={returnZero}
- />;
- //</div>;
+ backgroundColor={this.props.backgroundColor} />;
}
render() {
@@ -145,7 +132,10 @@ export class LinkDocPreview extends React.Component<Props> {
style={{
position: "absolute", left: this.props.location[0],
top: this.props.location[1], width: this.width(), height: this.height(),
- boxShadow: "black 2px 2px 1em", zIndex: 1000
+ zIndex: 1000,
+ border: "8px solid white", borderRadius: "7px",
+ boxShadow: "3px 3px 1.5px grey",
+ borderBottom: "8px solid white", borderRight: "8px solid white"
}}>
{this.targetDocView}
</div>;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 1c5825a8f..323da1233 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -268,7 +268,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
if (!this._pdfjsRequested) {
this._pdfjsRequested = true;
const promise = Pdfjs.getDocument(pdfUrl.url.href).promise;
- promise.then(action(pdf => { this._pdf = pdf; console.log("promise"); }));
+ promise.then(action(pdf => this._pdf = pdf));
}
}
diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx
index 9a1aefba9..45cdfc5ad 100644
--- a/src/client/views/nodes/SliderBox.tsx
+++ b/src/client/views/nodes/SliderBox.tsx
@@ -56,7 +56,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps, SliderDocume
style={{ boxShadow: this.layoutDoc.opacity === 0 ? undefined : StrCast(this.layoutDoc.boxShadow, "") }}>
<div className="sliderBox-mainButton" onContextMenu={this.specificContextMenu} style={{
background: StrCast(this.layoutDoc.backgroundColor), color: StrCast(this.layoutDoc.color, "black"),
- fontSize: NumCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
+ fontSize: StrCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing)
}} >
<Slider
mode={2}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fc63dfbf5..f87b28c7f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -174,19 +174,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
linkOnDeselect: Map<string, string> = new Map();
doLinkOnDeselect() {
+
Array.from(this.linkOnDeselect.entries()).map(entry => {
const key = entry[0];
const value = entry[1];
+
const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
DocServer.GetRefField(value).then(doc => {
DocServer.GetRefField(id).then(linkDoc => {
this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, _width: 500, _height: 500 }, value);
DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
- if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
- else DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "link to named target", id);
+ if (linkDoc) {
+ (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc;
+ } else {
+ DocUtils.MakeLink({ doc: this.rootDoc }, { doc: this.dataDoc[key] as Doc }, "portal link", "link to named target", id);
+ }
});
});
});
+
this.linkOnDeselect.clear();
}
@@ -473,7 +479,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
});
changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" });
- appearanceItems.push({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" });
+ appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" });
const uicontrols: ContextMenuProps[] = [];
uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" });
uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" });
@@ -483,10 +489,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt"
});
- appearanceItems.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" });
+ appearanceItems.push({ description: "UI Controls...", noexpand: true, subitems: uicontrols, icon: "asterisk" });
this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" });
Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
- appearanceItems.push({
+
+ !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+
+ const funcs: ContextMenuProps[] = [];
+
+ const highlighting: ContextMenuProps[] = [];
+ ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
+ highlighting.push({
+ description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+ e.stopPropagation();
+ if (FormattedTextBox._highlights.indexOf(option) === -1) {
+ FormattedTextBox._highlights.push(option);
+ } else {
+ FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+ }
+ this.updateHighlights();
+ }, icon: "expand-arrows-alt"
+ }));
+ funcs.push({ description: "highlighting...", noexpand: true, subitems: highlighting, icon: "hand-point-right" });
+ funcs.push({
description: "Convert to be a template style", event: () => {
if (!this.layoutDoc.isTemplateDoc) {
const title = StrCast(this.rootDoc.title);
@@ -511,29 +536,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc);
}, icon: "eye"
});
- !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
-
- const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" });
- funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" });
-
- const highlighting: ContextMenuProps[] = [];
- ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
- highlighting.push({
- description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
- e.stopPropagation();
- if (FormattedTextBox._highlights.indexOf(option) === -1) {
- FormattedTextBox._highlights.push(option);
- } else {
- FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
- }
- this.updateHighlights();
- }, icon: "expand-arrows-alt"
- }));
- funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" });
-
+ funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
this._downX = this._downY = Number.NaN;
}
@@ -944,6 +950,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
frag.forEach(node => nodes.push(marker(node)));
return Fragment.fromArray(nodes);
}
+
+
function addLinkMark(node: Node, title: string, linkId: string) {
if (!node.isText) {
const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId));
@@ -1168,7 +1176,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
} else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) &&
node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) {
- this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos!)));
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos)));
}
}
if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
@@ -1343,9 +1351,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const scale = this.props.ContentScaling() * NumCast(this.layoutDoc._viewScale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
const interactive = Doc.GetSelectedTool() === InkTool.None && !this.layoutDoc.isBackground;
- if (this.props.isSelected()) {
- setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), 0);
- } else if (FormattedTextBoxComment.textBox === this) {
+ setTimeout(() => this._editorView && RichTextMenu.Instance.updateFromDash(this._editorView, undefined, this.props), this.props.isSelected() ? 10 : 0); // need to make sure that we update a text box that is selected after updating the one that was deselected
+ if (!this.props.isSelected() && FormattedTextBoxComment.textBox === this) {
setTimeout(() => FormattedTextBoxComment.Hide(), 0);
}
const selPad = this.props.isSelected() ? -10 : 0;
@@ -1368,7 +1375,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"),
pointerEvents: interactive ? undefined : "none",
- fontSize: Cast(this.layoutDoc._fontSize, "number", null),
+ fontSize: Cast(this.layoutDoc._fontSize, "string", null),
fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"),
transition: "opacity 1s"
}}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
index 9089e7039..6a403cb17 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.scss
@@ -4,10 +4,74 @@
z-index: 20;
background: white;
border: 1px solid silver;
- border-radius: 2px;
+ border-radius: 7px;
margin-bottom: 7px;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
+ box-shadow: 3px 3px 1.5px grey;
+
+ .FormattedTextBoxComment {
+ background-color: white;
+ border: 8px solid white;
+
+ //display: flex;
+ .FormattedTextBoxComment-info {
+
+ margin-bottom: 9px;
+
+ .FormattedTextBoxComment-title {
+ padding-right: 4px;
+ float: left;
+
+ .FormattedTextBoxComment-description {
+ text-decoration: none;
+ font-style: italic;
+ color: rgb(95, 97, 102);
+ font-size: 10px;
+ padding-bottom: 4px;
+ margin-bottom: 5px;
+
+ }
+ }
+
+ .FormattedTextBoxComment-button {
+ display: inline;
+ padding-left: 6px;
+ padding-right: 6px;
+ padding-top: 2.5px;
+ padding-bottom: 2.5px;
+ width: 17px;
+ height: 17px;
+ margin: 0;
+ margin-right: 3px;
+ border-radius: 50%;
+ pointer-events: auto;
+ background-color: rgb(0, 0, 0);
+ color: rgb(255, 255, 255);
+ transition: transform 0.2s;
+ text-align: center;
+ position: relative;
+ font-size: 12px;
+
+ &:hover {
+ background-color: rgb(77, 77, 77);
+ cursor: pointer;
+ }
+ }
+ }
+
+ .FormattedTextBoxComment-preview-wrapper {
+ width: 170px;
+ height: 170px;
+ overflow: hidden;
+ //padding-top: 5px;
+ margin-top: 10px;
+ margin-bottom: 8px;
+ align-content: center;
+ justify-content: center;
+ background-color: rgb(160, 160, 160);
+ }
+ }
}
.FormattedTextBox-tooltip:before {
@@ -42,64 +106,4 @@
top: 50%;
right: 0;
transform: translateY(-50%);
-
- .FormattedTextBoxComment-button {
- width: 20px;
- height: 20px;
- margin: 0;
- margin-right: 6px;
- border-radius: 50%;
- pointer-events: auto;
- background-color: rgb(38, 40, 41);
- color: rgb(178, 181, 184);
- font-size: 65%;
- transition: transform 0.2s;
- text-align: center;
- position: relative;
-
- // margin-top: "auto";
- // margin-bottom: "auto";
- // background: black;
- // color: white;
- // display: inline-block;
- // border-radius: 18px;
- // font-size: 12.5px;
- // width: 18px;
- // height: 18px;
- // margin-top: auto;
- // margin-bottom: auto;
- // margin-right: 3px;
- // cursor: pointer;
- // transition: transform 0.2s;
-
- .FormattedTextBoxComment-fa-icon {
- margin-top: "auto";
- margin-bottom: "auto";
- background: black;
- color: white;
- display: inline-block;
- border-radius: 18px;
- font-size: 12.5px;
- width: 18px;
- height: 18px;
- margin-top: auto;
- margin-bottom: auto;
- margin-right: 3px;
- cursor: pointer;
- transition: transform 0.2s;
- // position: absolute;
- // top: 50%;
- // left: 50%;
- // transform: translate(-50%, -50%);
- }
-
- &:last-child {
- margin-right: 0;
- }
-
- &:hover {
- background: rgb(53, 146, 199);
- ;
- }
- }
} \ No newline at end of file
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index 56826e5c7..3687d5513 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -3,7 +3,7 @@ import { EditorState, Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import * as ReactDOM from 'react-dom';
import { Doc, DocCastAsync, Opt } from "../../../../fields/Doc";
-import { Cast, FieldValue, NumCast } from "../../../../fields/Types";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
@@ -21,6 +21,7 @@ import { action } from "mobx";
import { LinkManager } from "../../../util/LinkManager";
import { LinkDocPreview } from "../LinkDocPreview";
import { DocumentLinksButton } from "../DocumentLinksButton";
+import { Tooltip } from "@material-ui/core";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -85,13 +86,13 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
FormattedTextBoxComment.tooltip.style.maxWidth = "200px";
- FormattedTextBoxComment.tooltip.style.maxHeight = "206px";
+ FormattedTextBoxComment.tooltip.style.maxHeight = "235px";
FormattedTextBoxComment.tooltip.style.width = "100%";
FormattedTextBoxComment.tooltip.style.height = "100%";
FormattedTextBoxComment.tooltip.style.overflow = "hidden";
FormattedTextBoxComment.tooltip.style.display = "none";
FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
- FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
+ FormattedTextBoxComment.tooltip.onpointerdown = async (e: PointerEvent) => {
const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
const textBox = FormattedTextBoxComment.textBox;
if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
@@ -103,8 +104,22 @@ export class FormattedTextBoxComment {
if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
textBox.props.addDocTab(FormattedTextBoxComment.linkDoc, e.ctrlKey ? "inTab" : "onRight");
} else {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
- (doc: Doc, followLinkLocation: string) => textBox.props.addDocTab(doc, e.ctrlKey ? "inTab" : followLinkLocation));
+ const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc)), textBox.dataDoc) ?
+ Cast(FormattedTextBoxComment.linkDoc.anchor2, Doc) : (Cast(FormattedTextBoxComment.linkDoc.anchor1, Doc))
+ || FormattedTextBoxComment.linkDoc);
+ const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
+
+ if (FormattedTextBoxComment.linkDoc.follow) {
+ if (FormattedTextBoxComment.linkDoc.follow === "Default") {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+ } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in right tab") {
+ if (target) { textBox.props.addDocTab(target, "onRight"); }
+ } else if (FormattedTextBoxComment.linkDoc.follow === "Always open in new tab") {
+ if (target) { textBox.props.addDocTab(target, "inTab"); }
+ }
+ } else {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, doc => textBox.props.addDocTab(doc, "onRight"), false);
+ }
}
} else {
if (FormattedTextBoxComment.linkDoc.type !== DocumentType.LINK) {
@@ -241,55 +256,36 @@ export class FormattedTextBoxComment {
}
if (target?.author) {
FormattedTextBoxComment.showCommentbox("", view, nbef);
- const docPreview = <div style={{ backgroundColor: "white", border: "8px solid white" }}>
- {target.title}
- <div className="wrapper" style={{ float: "right" }}>
- <div title="Delete link" className="FormattedTextBoxComment-button" style={{
- display: "inline",
- paddingLeft: "6px",
- paddingRight: "6px",
- paddingTop: "2.5px",
- paddingBottom: "2.5px",
- width: "20px",
- height: "20px",
- margin: 0,
- marginRight: "6px",
- borderRadius: "50%",
- pointerEvents: "auto",
- backgroundColor: "rgb(38, 40, 41)",
- color: "rgb(178, 181, 184)",
- transition: "transform 0.2s",
- textAlign: "center",
- position: "relative"
- }} ref={(r) => this._deleteRef = r}>
- <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="trash"
- size="sm" /></div>
- <div title="Follow link" className="FormattedTextBoxComment-button" style={{
- display: "inline",
- paddingLeft: "6px",
- paddingRight: "6px",
- paddingTop: "2.5px",
- paddingBottom: "2.5px",
- width: "20px",
- height: "20px",
- margin: 0,
- marginRight: "6px",
- borderRadius: "50%",
- pointerEvents: "auto",
- backgroundColor: "rgb(38, 40, 41)",
- color: "rgb(178, 181, 184)",
- transition: "transform 0.2s",
- textAlign: "center",
- position: "relative"
- }} ref={(r) => this._followRef = r}>
- <FontAwesomeIcon className="FormattedTextBox-fa-icon" icon="arrow-right"
- size="sm" />
+
+ const title = StrCast(target.title).length > 16 ?
+ StrCast(target.title).substr(0, 16) + "..." : target.title;
+
+
+ const docPreview = <div className="FormattedTextBoxComment">
+ <div className="FormattedTextBoxComment-info">
+ <div className="FormattedTextBoxComment-title">
+ {title}
+ {FormattedTextBoxComment.linkDoc.description !== "" ? <p className="FormattedTextBoxComment-description">
+ {StrCast(FormattedTextBoxComment.linkDoc.description)}</p> : null}
</div>
- </div>
- <div className="wrapper" style={{
- maxWidth: "180px", maxHeight: "168px", overflow: "hidden",
- overflowY: "hidden", paddingTop: "5px"
- }}>
+ <div className="wrapper" style={{ float: "right" }}>
+
+ <Tooltip title={<><div className="dash-tooltip">Delete Link</div></>} placement="top">
+ <div className="FormattedTextBoxComment-button"
+ ref={(r) => this._deleteRef = r}>
+ <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="trash" color="white"
+ size="sm" /></div>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Follow Link</div></>} placement="top">
+ <div className="FormattedTextBoxComment-button"
+ ref={(r) => this._followRef = r}>
+ <FontAwesomeIcon className="FormattedTextBoxComment-fa-icon" icon="arrow-right" color="white"
+ size="sm" />
+ </div>
+ </Tooltip>
+ </div> </div>
+ <div className="FormattedTextBoxComment-preview-wrapper">
<ContentFittingDocumentView
Document={target}
LibraryPath={emptyPath}
@@ -307,17 +303,20 @@ export class FormattedTextBoxComment {
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
renderDepth={0}
- PanelWidth={() => Math.min(350, NumCast(target._width, 350))}
- PanelHeight={() => Math.min(250, NumCast(target._height, 250))}
+ PanelWidth={() => 175} //Math.min(350, NumCast(target._width, 350))}
+ PanelHeight={() => 175} //Math.min(250, NumCast(target._height, 250))}
focus={emptyFunction}
whenActiveChanged={returnFalse}
bringToFront={returnFalse}
ContentScaling={returnOne}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
+ NativeWidth={() => target._nativeWidth ? NumCast(target._nativeWidth) : 0}
+ NativeHeight={() => target._nativeHeight ? NumCast(target._nativeHeight) : 0}
/>
</div>
</div>;
+
+
+
FormattedTextBoxComment.showCommentbox("", view, nbef);
ReactDOM.render(docPreview, FormattedTextBoxComment.tooltipText);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 3f73ec436..8faf752b4 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -11,6 +11,7 @@ import { Doc, DataSym } from "../../../../fields/Doc";
import { FormattedTextBox } from "./FormattedTextBox";
import { Id } from "../../../../fields/FieldSymbols";
import { Docs } from "../../../documents/Documents";
+import { Utils } from "../../../../Utils";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -102,7 +103,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any
//Command to create a new Tab with a PDF of all the command shortcuts
bind("Mod-/", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- const newDoc = Docs.Create.PdfDocument("http://localhost:1050/assets/cheat-sheet.pdf", { _width: 300, _height: 300 });
+ const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _fitWidth: true, _width: 300, _height: 300 });
props.addDocTab(newDoc, "onRight");
});
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9075a6486..63f6fdc54 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -11,7 +11,7 @@ import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Doc } from "../../../../fields/Doc";
import { DarkPastelSchemaPalette, PastelSchemaPalette } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast, BoolCast } from "../../../../fields/Types";
+import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types";
import { unimplementedFunction, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { LinkManager } from "../../../util/LinkManager";
@@ -24,6 +24,7 @@ import "./RichTextMenu.scss";
import { schema } from "./schema_rts";
import { TraceMobx } from "../../../../fields/util";
import { UndoManager } from "../../../util/UndoManager";
+import { Tooltip } from "@material-ui/core";
const { toggleMark } = require("prosemirror-commands");
library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller);
@@ -112,6 +113,7 @@ export default class RichTextMenu extends AntimodeMenu {
{ node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType },
{ node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType },
{ node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "A.1", command: this.changeListType },
+ { node: schema.nodes.ordered_list.create({ mapStyle: "" }), title: "Set list type", label: "<none>", command: this.changeListType },
//{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType },
];
@@ -217,9 +219,9 @@ export default class RichTextMenu extends AntimodeMenu {
// finds font sizes and families in selection
getActiveAlignment() {
- if (this.view) {
+ if (this.view && this.TextView.props.isSelected(true)) {
const path = (this.view.state.selection.$from as any).path;
- for (let i = path.length - 3; i < path.length; i -= 3) {
+ for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) {
if (path[i]?.type === this.view.state.schema.nodes.paragraph) {
return path[i].attrs.align || "left";
}
@@ -230,15 +232,18 @@ export default class RichTextMenu extends AntimodeMenu {
// finds font sizes and families in selection
getActiveListStyle() {
- if (this.view) {
+ if (this.view && this.TextView.props.isSelected(true)) {
const path = (this.view.state.selection.$from as any).path;
for (let i = 0; i < path.length; i += 3) {
if (path[i].type === this.view.state.schema.nodes.ordered_list) {
return path[i].attrs.mapStyle;
}
}
+ if (this.view.state.selection.$from.nodeAfter?.type === this.view.state.schema.nodes.ordered_list) {
+ return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle;
+ }
}
- return "decimal";
+ return "";
}
// finds font sizes and families in selection
@@ -247,16 +252,21 @@ export default class RichTextMenu extends AntimodeMenu {
const activeFamilies: string[] = [];
const activeSizes: string[] = [];
- const state = this.view.state;
- const pos = this.view.state.selection.$from;
- const ref_node = this.reference_node(pos);
- if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
- ref_node.marks.forEach(m => {
- m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
- m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
- });
+ if (this.TextView.props.isSelected(true)) {
+ const state = this.view.state;
+ const pos = this.view.state.selection.$from;
+ const ref_node = this.reference_node(pos);
+ if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) {
+ ref_node.marks.forEach(m => {
+ m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family);
+ m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt");
+ });
+ }
+ !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily))));
+ !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize))));
}
-
+ !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily)));
+ !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize)));
return { activeFamilies, activeSizes };
}
@@ -269,14 +279,14 @@ export default class RichTextMenu extends AntimodeMenu {
//finds all active marks on selection in given group
getActiveMarksOnSelection() {
- if (!this.view) return;
+ let activeMarks: MarkType[] = [];
+ if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks;
const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
//current selection
const { empty, ranges, $to } = this.view.state.selection as TextSelection;
const state = this.view.state;
- let activeMarks: MarkType[] = [];
if (!empty) {
activeMarks = markGroup.filter(mark => {
const has = false;
@@ -348,22 +358,20 @@ export default class RichTextMenu extends AntimodeMenu {
}
return (
- <button className={"antimodeMenu-button" + (isActive ? " active" : "")} key={title} title={title} onPointerDown={onClick}>
- <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
- </button>
+ <Tooltip title={<div className="dash-tooltip">{title}</div>} key={title} placement="bottom">
+ <button className={"antimodeMenu-button" + (isActive ? " active" : "")} onPointerDown={onClick}>
+ <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
+ </button>
+ </Tooltip>
);
}
- createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
+ createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {
const items = options.map(({ title, label, hidden, style }) => {
if (hidden) {
- return label === activeOption ?
- <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
- <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+ return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
}
- return label === activeOption ?
- <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
- <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+ return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
});
const self = this;
@@ -372,37 +380,47 @@ export default class RichTextMenu extends AntimodeMenu {
e.preventDefault();
self.TextView.endUndoTypingBatch();
options.forEach(({ label, mark, command }) => {
- if (e.target.value === label) {
- UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
+ if (e.target.value === label && mark) {
+ if (!self.TextView.props.isSelected(true)) {
+ switch (mark.type) {
+ case schema.marks.pFontFamily: setter(Doc.UserDoc().fontFamily = mark.attrs.family); break;
+ case schema.marks.pFontSize: setter(Doc.UserDoc().fontSize = mark.attrs.fontSize.toString() + "pt"); break;
+ }
+ }
+ else UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown");
}
});
}
- return <select onChange={onChange} key={key}>{items}</select>;
+ return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
+ <select onChange={onChange} value={activeOption}>{items}</select>
+ </Tooltip>;
}
- createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element {
- const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : "A.1";
+ createNodesDropdown(activeMap: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string, setter: (val: string) => {}): JSX.Element {
+ const activeOption = activeMap === "bullet" ? ":" : activeMap === "decimal" ? "1.1" : activeMap === "multi" ? "A.1" : "<none>";
const items = options.map(({ title, label, hidden, style }) => {
if (hidden) {
- return label === activeOption ?
- <option value={label} title={title} key={label} style={style ? style : {}} selected hidden>{label}</option> :
- <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
+ return <option value={label} title={title} key={label} style={style ? style : {}} hidden>{label}</option>;
}
- return label === activeOption ?
- <option value={label} title={title} key={label} style={style ? style : {}} selected>{label}</option> :
- <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
+ return <option value={label} title={title} key={label} style={style ? style : {}}>{label}</option>;
});
const self = this;
function onChange(val: string) {
self.TextView.endUndoTypingBatch();
options.forEach(({ label, node, command }) => {
- if (val === label) {
- UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
+ if (val === label && node) {
+ if (self.TextView.props.isSelected(true)) {
+ UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown");
+ setter(val);
+ }
}
});
}
- return <select onChange={e => onChange(e.target.value)} key={key}>{items}</select>;
+
+ return <Tooltip key={key} title={<div className="dash-tooltip">{key}</div>} placement="bottom">
+ <select value={activeOption} onChange={e => onChange(e.target.value)}>{items}</select>
+ </Tooltip>;
}
changeFontSize = (mark: Mark, view: EditorView) => {
@@ -416,10 +434,21 @@ export default class RichTextMenu extends AntimodeMenu {
// TODO: remove doesn't work
//remove all node type and apply the passed-in one to the selected text
changeListType = (nodeType: Node | undefined) => {
- if (!this.view) return;
+ if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return;
+
+ const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list;
+ let inList: any = undefined;
+ let fromList = -1;
+ const path: any = Array.from((this.view.state.selection.$from as any).path);
+ for (let i = 0; i < path.length; i++) {
+ if (path[i]?.type === schema.nodes.ordered_list) {
+ inList = path[i];
+ fromList = path[i - 1];
+ }
+ }
const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks());
- if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
+ if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => {
const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
@@ -427,12 +456,12 @@ export default class RichTextMenu extends AntimodeMenu {
this.view!.dispatch(tx2);
})) {
const tx2 = this.view.state.tr;
- if (nodeType && this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list) {
- const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view.state.selection.from, this.view.state.selection.to);
+ if (nodeType && (inList || nextIsOL)) {
+ const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from,
+ inList ? fromList + inList.nodeSize : this.view.state.selection.to);
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
-
- this.view.dispatch(tx3.setSelection(new NodeSelection(tx3.doc.resolve(this.view.state.selection.$from.pos))));
+ this.view.dispatch(tx3);
}
}
}
@@ -448,13 +477,13 @@ export default class RichTextMenu extends AntimodeMenu {
return true;
}
alignCenter = (state: EditorState<any>, dispatch: any) => {
- return this.alignParagraphs(state, "center", dispatch);
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "center", dispatch);
}
alignLeft = (state: EditorState<any>, dispatch: any) => {
- return this.alignParagraphs(state, "left", dispatch);
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "left", dispatch);
}
alignRight = (state: EditorState<any>, dispatch: any) => {
- return this.alignParagraphs(state, "right", dispatch);
+ return this.TextView.props.isSelected(true) && this.alignParagraphs(state, "right", dispatch);
}
alignParagraphs(state: EditorState<any>, align: "left" | "right" | "center", dispatch: any) {
@@ -574,10 +603,11 @@ export default class RichTextMenu extends AntimodeMenu {
label = "No marks are currently stored";
}
- const button =
- <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
+ const button = <Tooltip title={<div className="dash-tooltip">style brush</div>} placement="bottom">
+ <button className="antimodeMenu-button" onPointerDown={onBrushClick} style={this.brushMarks?.size > 0 ? { backgroundColor: "121212" } : {}}>
<FontAwesomeIcon icon="paint-roller" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.brushMarks?.size > 0 ? 45 : 0}deg)` }} />
- </button>;
+ </button>
+ </Tooltip>;
const dropdownContent =
<div className="dropdown">
@@ -646,11 +676,12 @@ export default class RichTextMenu extends AntimodeMenu {
self.TextView.EditorView!.focus();
}
- const button =
- <button className="antimodeMenu-button color-preview-button" title="" onPointerDown={onColorClick}>
+ const button = <Tooltip title={<div className="dash-tooltip">set font color</div>} placement="bottom">
+ <button className="antimodeMenu-button color-preview-button" onPointerDown={onColorClick}>
<FontAwesomeIcon icon="palette" size="lg" />
<div className="color-preview" style={{ backgroundColor: this.activeFontColor }}></div>
- </button>;
+ </button>
+ </Tooltip>;
const dropdownContent =
<div className="dropdown" >
@@ -699,11 +730,12 @@ export default class RichTextMenu extends AntimodeMenu {
UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter");
}
- const button =
- <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={onHighlightClick}>
+ const button = <Tooltip title={<div className="dash-tooltip">set highlight color</div>} placement="bottom">
+ <button className="antimodeMenu-button color-preview-button" key="highilghter-button" onPointerDown={onHighlightClick}>
<FontAwesomeIcon icon="highlighter" size="lg" />
<div className="color-preview" style={{ backgroundColor: this.activeHighlightColor }}></div>
- </button>;
+ </button>
+ </Tooltip>;
const dropdownContent =
<div className="dropdown">
@@ -742,7 +774,9 @@ export default class RichTextMenu extends AntimodeMenu {
const link = this.currentLink ? this.currentLink : "";
- const button = <FontAwesomeIcon icon="link" size="lg" />;
+ const button = <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom">
+ <div><FontAwesomeIcon icon="link" size="lg" /> </div>
+ </Tooltip>;
const dropdownContent =
<div className="dropdown link-menu">
@@ -753,9 +787,7 @@ export default class RichTextMenu extends AntimodeMenu {
<button className="remove-button" onPointerDown={e => this.deleteLink()}>Remove link</button>
</div>;
- return (
- <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />
- );
+ return <ButtonDropdown view={this.view} key={"link button"} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} />;
}
async getTextLinkTargetTitle() {
@@ -882,22 +914,22 @@ export default class RichTextMenu extends AntimodeMenu {
render() {
TraceMobx();
- const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[
+ const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
!this.collapsed ? this.getDragger() : (null),
- !this.Pinned ? (null) : <> {[
+ !this.Pinned ? (null) : <div key="frag1"> {[
this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
- <div className="richTextMenu-divider" />
- ]}</>,
+ <div className="richTextMenu-divider" key="divider" />
+ ]}</div>,
this.createColorButton(),
this.createHighlighterButton(),
this.createLinkButton(),
this.createBrushButton(),
- <div className="richTextMenu-divider" />,
+ <div className="richTextMenu-divider" key="divider 2" />,
this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
@@ -907,20 +939,20 @@ export default class RichTextMenu extends AntimodeMenu {
this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
]}</div>;
- const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2">
+ const row2 = <div className="antimodeMenu-row row-2" key="row2">
{this.collapsed ? this.getDragger() : (null)}
- <div key="row" style={{ display: this.collapsed ? "none" : undefined }}>
- <div className="richTextMenu-divider" />,
- {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"),
- this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"),
- <div className="richTextMenu-divider" />,
- this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"),
+ <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
+ <div className="richTextMenu-divider" key="divider 3" />,
+ {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => this.activeFontSize = val)),
+ this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => this.activeFontFamily = val)),
+ <div className="richTextMenu-divider" key="divider 4" />,
+ this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", action((val: string) => this.activeListType = val)),
this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule),
- <div className="richTextMenu-divider" />,]}
+ <div className="richTextMenu-divider" key="divider 5" />,]}
</div>
- <div key="button">
+ <div key="collapser">
{/* <div key="collapser">
<button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
<FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index ca30dde9d..ef0fead4a 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -90,7 +90,7 @@ export class RichTextRules {
textDoc.inlineTextCount = numInlines + 1;
const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to
const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation
- const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: 9, title: "inline comment" });
+ const textDocInline = Docs.Create.TextDocument("", { layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9pt", title: "inline comment" });
textDocInline.title = inlineFieldKey; // give the annotation its own title
textDocInline.customTitle = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc
textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4e5fdbfbb..30c51d9ca 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -262,7 +262,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
const eventBus = new PDFJSViewer.EventBus(true);
eventBus._on("pagesinit", this.pagesinit);
eventBus._on("pagerendered", action(() => this._showCover = this._showWaiting = false));
- const pdfLinkService = new PDFJSViewer.PDFLinkService();
+ const pdfLinkService = new PDFJSViewer.PDFLinkService({ eventBus });
const pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, eventBus });
this._pdfViewer = new PDFJSViewer.PDFViewer({
container: this._mainCont.current,
@@ -392,7 +392,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
search = (searchString: string, fwd: boolean, clear: boolean = false) => {
if (clear) {
- this._pdfViewer.findController.executeCommand('reset', {});
+ this._pdfViewer.findController.executeCommand('reset', { query: "" });
} else if (!searchString) {
fwd ? this.nextAnnotation() : this.prevAnnotation();
} else if (this._pdfViewer.pageViewsReady) {
@@ -637,7 +637,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
@action
onZoomWheel = (e: React.WheelEvent) => {
- if (this.active()) {
+ if (this.active(true)) {
e.stopPropagation();
if (e.ctrlKey) {
const curScale = Number(this._pdfViewer.currentScaleValue);
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index fc040f67b..c503b0678 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -16,6 +16,7 @@ import "./PresElementBox.scss";
import React = require("react");
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { PresBox } from "../nodes/PresBox";
+import { DocumentType } from "../../documents/DocumentTypes";
export const presSchema = createSchema({
presentationTargetDoc: Doc,
@@ -100,7 +101,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
e.stopPropagation();
this.rootDoc.presProgressivize = !this.rootDoc.presProgressivize;
const rootTarget = Cast(this.rootDoc.presentationTargetDoc, Doc, null);
- const docs = DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]);
+ const docs = rootTarget.type === DocumentType.COL ? DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget)]) :
+ DocListCast(rootTarget[Doc.LayoutFieldKey(rootTarget) + "-annotations"]);
if (this.rootDoc.presProgressivize) {
rootTarget.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(docs, docs.length, true);
@@ -268,4 +270,4 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 97f62c9d4..ddffb56c3 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -3,7 +3,6 @@ import { ScriptField } from "./ScriptField";
import { Doc } from "./Doc";
import { DateField } from "./DateField";
import { SchemaHeaderField } from "./SchemaHeaderField";
-import { Schema } from "prosemirror-model";
export const documentSchema = createSchema({
// content properties
@@ -55,7 +54,7 @@ export const documentSchema = createSchema({
_columnsHideIfEmpty: "boolean", // whether empty stacking view column headings should be hidden
_columnHeaders: listSpec(SchemaHeaderField), // header descriptions for stacking/masonry
_schemaHeaders: listSpec(SchemaHeaderField), // header descriptions for schema views
- _fontSize: "number",
+ _fontSize: "string",
_fontFamily: "string",
_sidebarWidthPercent: "string", // percent of text window width taken up by sidebar
@@ -66,6 +65,7 @@ export const documentSchema = createSchema({
color: "string", // foreground color of document
fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view
fontSize: "string",
+ hidden: "boolean", // whether a document should not be displayed
isInkMask: "boolean", // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through)
layout: "string", // this is the native layout string for the document. templates can be added using other fields and setting layoutKey below
layoutKey: "string", // holds the field key for the field that actually holds the current lyoat
@@ -73,6 +73,9 @@ export const documentSchema = createSchema({
opacity: "number", // opacity of document
strokeWidth: "number",
strokeBezier: "number",
+ strokeStartMarker: "string",
+ strokeEndMarker: "string",
+ strokeDash: "string",
textTransform: "string",
treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden
treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree
@@ -85,6 +88,8 @@ export const documentSchema = createSchema({
onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped.
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
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/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 5ea626d52..e13c91db9 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -55,7 +55,7 @@ export class Uploader extends React.Component<ImageUploadProps> {
doc = Docs.Create.VideoDocument(path, { _nativeWidth: 400, _width: 400, title: name });
// Case 2: File is a PDF document
} else if (file.type === "application/pdf") {
- doc = Docs.Create.PdfDocument(path, { _nativeWidth: 400, _width: 400, title: name });
+ doc = Docs.Create.PdfDocument(path, { _nativeWidth: 400, _width: 400, _fitWidth: true, title: name });
// Case 3: File is another document type (most likely Image)
} else {
doc = Docs.Create.ImageDocument(path, { _nativeWidth: 400, _width: 400, title: name });
diff --git a/src/server/websocket.ts b/src/server/websocket.ts
index d55c2e198..f63a35e43 100644
--- a/src/server/websocket.ts
+++ b/src/server/websocket.ts
@@ -180,7 +180,14 @@ export namespace WebSocket {
function barReceived(socket: SocketIO.Socket, userEmail: string) {
clients[userEmail] = new Client(userEmail.toString());
- console.log(green(`user ${userEmail} has connected to the web socket`));
+ const currentdate = new Date();
+ const datetime = currentdate.getDate() + "/"
+ + (currentdate.getMonth() + 1) + "/"
+ + currentdate.getFullYear() + " @ "
+ + currentdate.getHours() + ":"
+ + currentdate.getMinutes() + ":"
+ + currentdate.getSeconds();
+ console.log(green(`user ${userEmail} has connected to the web socket at: ${datetime}`));
socketMap.set(socket, userEmail);
}