aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.ts7
-rw-r--r--src/client/documents/Documents.ts77
-rw-r--r--src/client/util/CurrentUserUtils.ts42
-rw-r--r--src/client/util/DocumentManager.ts28
-rw-r--r--src/client/util/DragManager.ts6
-rw-r--r--src/client/util/LinkManager.ts12
-rw-r--r--src/client/views/DocComponent.tsx4
-rw-r--r--src/client/views/DocumentButtonBar.tsx46
-rw-r--r--src/client/views/DocumentDecorations.scss498
-rw-r--r--src/client/views/DocumentDecorations.tsx65
-rw-r--r--src/client/views/GlobalKeyHandler.ts17
-rw-r--r--src/client/views/LightboxView.tsx17
-rw-r--r--src/client/views/MainView.tsx1
-rw-r--r--src/client/views/SidebarAnnos.tsx2
-rw-r--r--src/client/views/StyleProvider.tsx44
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx4
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx11
-rw-r--r--src/client/views/collections/CollectionPileView.tsx18
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx6
-rw-r--r--src/client/views/collections/CollectionSubView.tsx7
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx9
-rw-r--r--src/client/views/collections/CollectionTreeView.scss6
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx5
-rw-r--r--src/client/views/collections/TabDocView.tsx10
-rw-r--r--src/client/views/collections/TreeView.scss2
-rw-r--r--src/client/views/collections/TreeView.tsx86
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx30
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx62
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx11
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss4
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx5
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx10
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx1
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.scss3
-rw-r--r--src/client/views/nodes/DocumentView.tsx74
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.tsx18
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/LabelBigText.js34
-rw-r--r--src/client/views/nodes/LabelBox.scss2
-rw-r--r--src/client/views/nodes/LabelBox.tsx27
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx10
-rw-r--r--src/client/views/nodes/PDFBox.tsx57
-rw-r--r--src/client/views/nodes/VideoBox.scss1
-rw-r--r--src/client/views/nodes/VideoBox.tsx25
-rw-r--r--src/client/views/nodes/WebBox.tsx8
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx24
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx21
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss2
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx8
-rw-r--r--src/client/views/pdf/PDFViewer.tsx4
-rw-r--r--src/client/views/topbar/TopBar.tsx26
-rw-r--r--src/fields/Doc.ts11
-rw-r--r--src/server/ApiManagers/UploadManager.ts12
57 files changed, 900 insertions, 629 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 77095005b..38eb51529 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -399,6 +399,12 @@ export function timenow() {
return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
}
+export function incrementTitleCopy(title: string) {
+ const numstr = title.match(/.*(\{([0-9]*)\})+/);
+ const copyNumStr = `{${1 + (numstr ? (+numstr[2]) : 0)}}`;
+ return (numstr ? title.replace(numstr[1], "") : title) + copyNumStr;
+}
+
export function formatTime(time: number) {
time = Math.round(time);
const hours = Math.floor(time / 60 / 60);
@@ -604,6 +610,7 @@ export function DashColor(color: string) {
export function lightOrDark(color: any) {
if (color === "transparent") return "gray";
+ if (color.startsWith?.("linear")) return "black";
const nonAlphaColor = color.startsWith("#") ? (color as string).substring(0, 7) :
color.startsWith("rgba") ? color.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : color;
const col = DashColor(nonAlphaColor).rgb();
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index bb60586eb..a19530c92 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -14,7 +14,7 @@ import { Cast, NumCast, StrCast } from "../../fields/Types";
import { AudioField, ImageField, MapField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField";
import { SharingPermissions } from "../../fields/util";
import { Upload } from "../../server/SharedMediaTypes";
-import { OmitKeys, Utils } from "../../Utils";
+import { OmitKeys, Utils, aggregateBounds } from "../../Utils";
import { YoutubeBox } from "../apis/youtube/YoutubeBox";
import { DocServer } from "../DocServer";
import { Networking } from "../Network";
@@ -117,6 +117,7 @@ export class DocumentOptions {
_lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
_lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
_freeformLOD?: boolean; // whether to use LOD to render a freeform document
+ _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target
_showTitle?: string; // field name to display in header (:hover is an optional suffix)
_showCaption?: string; // which field to display in the caption area. leave empty to have no caption
_scrollTop?: number; // scroll location for pdfs
@@ -139,6 +140,8 @@ export class DocumentOptions {
_itemIndex?: number; // which item index the carousel viewer is showing
_showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts
_singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document)
+ _minFontSize?: number; // minimum font size for labelBoxes
+ _maxFontSize?: number; // maximum font size for labelBoxes
_columnWidth?: number;
_columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden
_fontSize?: string;
@@ -221,6 +224,7 @@ export class DocumentOptions {
dockingConfig?: string;
annotationOn?: Doc;
isPushpin?: boolean;
+ isGroup?: boolean; // whether a collection should use a grouping UI behavior
_removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
// BACKGROUND GRID
@@ -280,6 +284,7 @@ export class DocumentOptions {
treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view
treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
+ treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically
// Action Button
buttonMenu?: boolean; // whether a action button should be displayed
@@ -361,7 +366,7 @@ export namespace Docs {
[DocumentType.RTF, {
layout: { view: FormattedTextBox, dataField: "text" },
options: {
- _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true,
+ _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true,
links: "@links(self)"
}
}],
@@ -441,7 +446,7 @@ export namespace Docs {
}],
[DocumentType.LABEL, {
layout: { view: LabelBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
+ options: { links: "@links(self)", _singleLine: true }
}],
[DocumentType.EQUATION, {
layout: { view: EquationBox, dataField: defaultDataKey },
@@ -456,8 +461,8 @@ export namespace Docs {
options: { links: "@links(self)" }
}],
[DocumentType.SLIDER, {
- layout: { view: SliderBox, dataField: defaultDataKey },
- options: { links: "@links(self)" }
+ layout: { view: SliderBox, dataField: defaultDataKey, },
+ options: { links: "@links(self)", treeViewGrowsHorizontally: true }
}],
[DocumentType.PRES, {
layout: { view: PresBox, dataField: defaultDataKey },
@@ -805,7 +810,7 @@ export namespace Docs {
}
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Freeform }, id);
+ const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _viewType: CollectionViewType.Freeform }, id);
documents.map(d => d.context = inst);
return inst;
}
@@ -1315,22 +1320,25 @@ export namespace DocUtils {
const _height = NumCast(doc._height);
const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false };
- let fieldTemplate: Opt<Doc>;
- if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
- fieldTemplate = Docs.Create.TextDocument("", options);
- } else if (doc.data instanceof PdfField) {
- fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
- } else if (doc.data instanceof VideoField) {
- fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
- } else if (doc.data instanceof AudioField) {
- fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
- } else if (doc.data instanceof ImageField) {
- fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
- }
- const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
-
- fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
- docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ if (docLayoutTemplate) {
+ Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined);
+ } else {
+ let fieldTemplate: Opt<Doc>;
+ if (doc.data instanceof RichTextField || typeof (doc.data) === "string") {
+ fieldTemplate = Docs.Create.TextDocument("", options);
+ } else if (doc.data instanceof PdfField) {
+ fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options);
+ } else if (doc.data instanceof VideoField) {
+ fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options);
+ } else if (doc.data instanceof AudioField) {
+ fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options);
+ } else if (doc.data instanceof ImageField) {
+ fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
+ }
+ const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) });
+ fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate);
+ docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined);
+ }
}
export function makeCustomView(doc: Doc, custom: boolean, layout: string) {
Doc.setNativeView(doc);
@@ -1344,26 +1352,31 @@ export namespace DocUtils {
if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", "");
}
- export function pileup(docList: Doc[], x?: number, y?: number, create: boolean = true) {
+ export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) {
let w = 0, h = 0;
runInAction(() => {
docList.forEach(d => {
DocUtils.iconify(d);
- w = Math.max(d[WidthSym](), w);
- h = Math.max(d[HeightSym](), h);
+ w = Math.max(NumCast(d._width), w);
+ h = Math.max(NumCast(d._height), h);
+ });
+ docList.forEach((d, i) => {
+ d.x = Math.cos(Math.PI * 2 * i / docList.length) * size;
+ d.y = Math.sin(Math.PI * 2 * i / docList.length) * size;
+ d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
});
- h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable
+ const aggBounds = aggregateBounds(docList.map(d => ({ x: NumCast(d.x), y: NumCast(d.y), width: NumCast(d._width), height: NumCast(d._height) })), 0, 0);
docList.forEach((d, i) => {
- d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2;
- d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2;
+ d.x = NumCast(d.x) - ((aggBounds.r + aggBounds.x) / 2);
+ d.y = NumCast(d.y) - ((aggBounds.b + aggBounds.y) / 2);
d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
});
});
if (create) {
- const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, });
- newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55;
- newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55;
- newCollection._width = newCollection._height = 110;
+ const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, });
+ newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size;
+ newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size;
+ newCollection._width = newCollection._height = size * 2;
newCollection._jitterRotation = 10;
return newCollection;
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index af731ce9f..c7309d15e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -9,7 +9,7 @@ import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
import { ComputedField, ScriptField } from "../../fields/ScriptField";
import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types";
-import { nullAudio } from "../../fields/URLField";
+import { ImageField, nullAudio } from "../../fields/URLField";
import { SharingPermissions } from "../../fields/util";
import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
@@ -292,7 +292,7 @@ export class CurrentUserUtils {
if (doc["template-icon-view"] === undefined) {
const iconView = Docs.Create.LabelDocument({
title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray",
- _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
+ _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
});
// Docs.Create.TextDocument("", {
// title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" })
@@ -303,8 +303,9 @@ export class CurrentUserUtils {
}
if (doc["template-icon-view-rtf"] === undefined) {
const iconRtfView = Docs.Create.LabelDocument({
- title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"),
- _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
+ title: "icon_" + DocumentType.RTF, _showTitle: "creationDate", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"),
+ _singleLine: false, _minFontSize: 18, _maxFontSize: 24,
+ _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
});
iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
@@ -319,19 +320,41 @@ export class CurrentUserUtils {
}
if (doc["template-icon-view-img"] === undefined) {
const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
- title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
+ title: "data", _width: 150, isTemplateDoc: true, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true
});
iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG);
doc["template-icon-view-img"] = new PrefetchProxy(iconImageView);
}
if (doc["template-icon-view-col"] === undefined) {
- const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
+ const iconColView = Docs.Create.ImageDocument("", { title: "icon", _showTitle: "title", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL);
+ const proto = iconColView.proto as Doc;
+ proto["icon-nativeWidth"] = 360 / 4;
+ proto["icon-nativeHeight"] = 270 / 4;
+ proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
}
+ if (doc["template-icon-view-video"] === undefined) {
+ const iconVidView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
+ iconVidView.isTemplateDoc = makeTemplate(iconVidView, true, "icon_" + DocumentType.VID);
+ const proto = iconVidView.proto as Doc;
+ proto["icon-nativeWidth"] = 360 / 4;
+ proto["icon-nativeHeight"] = 270 / 4;
+ proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
+ doc["template-icon-view-video"] = new PrefetchProxy(iconVidView);
+ }
+ if (doc["template-icon-view-pdf"] === undefined) {
+ const iconPdfView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true });
+ iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF);
+ const proto = iconPdfView.proto as Doc;
+ proto["icon-nativeWidth"] = 360 / 4;
+ proto["icon-nativeHeight"] = 270 / 4;
+ proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png");
+ doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView);
+ }
if (doc["template-icons"] === undefined) {
doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true }));
+ doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-video"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true }));
} else {
const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc,
@@ -366,7 +389,7 @@ export class CurrentUserUtils {
const textDoc = Docs.Create.TreeDocument([], {
title: "Slide", _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true,
allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true,
- backgroundColor: "transparent", system: true, cloneFieldFilter: new List<string>(["system"])
+ backgroundColor: "white", system: true, cloneFieldFilter: new List<string>(["system"])
});
Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text');
FormattedTextBox.SelectOnLoad = textDoc[Id];
@@ -1321,9 +1344,6 @@ export class CurrentUserUtils {
// undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss)
doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme;
}
- if (doc.activeCollectionBackground === "white") { // temporary to avoid having to rebuild the databse for old accounts that have this set by default.
- doc.activeCollectionBackground = undefined;
- }
return doc;
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 93d0c6936..607a3d6bf 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -168,15 +168,21 @@ export class DocumentManager {
originalTarget = originalTarget ?? targetDoc;
const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView;
const docView = getFirstDocView(targetDoc, originatingDoc);
- const wasHidden = targetDoc?.hidden; //
- if (wasHidden && targetDoc) runInAction(() => targetDoc.hidden = false); // if the target is hidden, un-hide it here.
+ const wasHidden = targetDoc.hidden; //
+ if (wasHidden) {
+ runInAction(() => {
+ targetDoc.hidden = false;
+ docView?.props.bringToFront(targetDoc);
+ }); // if the target is hidden, un-hide it here.
+ }
const focusAndFinish = (didFocus: boolean) => {
+ const finalTargetDoc = docView?.Document ?? targetDoc;
if (originatingDoc?.isPushpin) {
if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal
- targetDoc.hidden = !targetDoc.hidden;
+ finalTargetDoc.hidden = !finalTargetDoc.hidden;
}
} else {
- targetDoc.hidden && (targetDoc.hidden = undefined);
+ finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined);
!noSelect && docView?.select(false);
}
finished?.();
@@ -191,7 +197,14 @@ export class DocumentManager {
(wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above
const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView;
if (!docView && annoContainerView) {
- annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
+ if (annoContainerView.props.Document.layoutKey === "layout_icon") {
+ annoContainerView.iconify(() => this.jumpToDocument(
+ targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
+ finished, originalTarget, noSelect, presZoom));
+ return;
+ } else {
+ annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below
+ }
}
if (focusView) {
!noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox
@@ -211,6 +224,11 @@ export class DocumentManager {
targetDocContextView.props.focus(targetDocContextView.rootDoc, {
willZoom, afterFocus: async () => {
targetDocContext._viewTransition = undefined;
+ if (targetDocContext.layoutKey === "layout_icon") {
+ targetDocContextView.iconify(() => this.jumpToDocument(
+ targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc,
+ finished, originalTarget, noSelect, presZoom));
+ }
return ViewAdjustment.doNothing;
}
});
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 411fc6d11..13e5940d6 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -380,8 +380,8 @@ export namespace DragManager {
}
}
const rect = ele.getBoundingClientRect();
- const scaleX = rect.width / ele.offsetWidth;
- const scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX;
+ const scaleX = rect.width / (ele.offsetWidth || rect.width);
+ const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX;
elesCont.left = Math.min(rect.left, elesCont.left);
elesCont.top = Math.min(rect.top, elesCont.top);
@@ -405,7 +405,7 @@ export namespace DragManager {
Array.from(pdfBox).filter(pb => pb.width && pb.height).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
}
[dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele =>
- ele.hasAttribute("style") && ((ele as any).style.pointerEvents = "none"));
+ (ele as any).style && ((ele as any).style.pointerEvents = "none"));
dragDiv.appendChild(dragElement);
if (dragElement !== ele) {
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index df2c02a8d..0f3da0548 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -221,7 +221,9 @@ export class LinkManager {
const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0);
const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs);
- const followLinks = linkDocList.length ? (sourceDoc.isPushpin ? linkDocList : [linkDocList[0]]) : [];
+ const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1);
+ var count = 0;
+ const allFinished = () => ++count === followLinks.length && finished?.();
followLinks.forEach(async linkDoc => {
if (linkDoc) {
const target = (sourceDoc === linkDoc.anchor1 ? linkDoc.anchor2 : sourceDoc === linkDoc.anchor2 ? linkDoc.anchor1 :
@@ -232,20 +234,20 @@ export class LinkManager {
const tour = DocListCast(target[fieldKey]).reverse();
LightboxView.SetLightboxDoc(currentContext, undefined, tour);
setTimeout(LightboxView.Next);
- finished?.();
+ allFinished();
} else {
const containerAnnoDoc = Cast(target.annotationOn, Doc, null);
const containerDoc = containerAnnoDoc || target;
const containerDocContext = Cast(containerDoc?.context, Doc, null);
const targetContext = LightboxView.LightboxDoc ? containerAnnoDoc || containerDocContext : containerDocContext;
const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined;
- DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished);
+ DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, allFinished);
}
} else {
- finished?.();
+ allFinished();
}
} else {
- finished?.();
+ allFinished();
}
});
}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 73a261c40..79aaf2158 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -11,6 +11,7 @@ import { DocUtils } from '../documents/Documents';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
import { InteractionUtils } from '../util/InteractionUtils';
import { UndoManager } from '../util/UndoManager';
+import { DocumentView } from './nodes/DocumentView';
import { Touchable } from './Touchable';
@@ -41,6 +42,7 @@ interface ViewBoxBaseProps {
Document: Doc;
DataDoc?: Doc;
ContainingCollectionDoc: Opt<Doc>;
+ DocumentView?: () => DocumentView;
fieldKey: string;
layerProvider?: (doc: Doc, assign?: boolean) => boolean;
isSelected: (outsideReaction?: boolean) => boolean;
@@ -63,7 +65,7 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
// key where data is stored
@computed get fieldKey() { return this.props.fieldKey; }
- lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result;
+ lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc }).result;
isContentActive = (outsideReaction?: boolean) => (
this.props.isContentActive?.() === false ? false :
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index aa9318310..9b95f1525 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -215,28 +215,30 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
pinWithView = (targetDoc: Doc) => {
if (targetDoc) {
TabDocView.PinDoc(targetDoc);
- const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
- const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking;
- const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG);
- if (scrollable) {
- const scroll = targetDoc._scrollTop;
- activeDoc.presPinView = true;
- activeDoc.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeDoc.presPinTimecode = targetDoc._currentTimecode;
- } else if (pannable) {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeDoc.presPinView = true;
- activeDoc.presPinViewX = x;
- activeDoc.presPinViewY = y;
- activeDoc.presPinViewScale = scale;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const width = targetDoc._clipWidth;
- activeDoc.presPinClipWidth = width;
- activeDoc.presPinView = true;
- }
+ setTimeout(() => {
+ const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1];
+ const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking;
+ const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG);
+ if (scrollable) {
+ const scroll = targetDoc._scrollTop;
+ activeDoc.presPinView = true;
+ activeDoc.presPinViewScroll = scroll;
+ } else if (targetDoc.type === DocumentType.VID) {
+ activeDoc.presPinTimecode = targetDoc._currentTimecode;
+ } else if (pannable) {
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeDoc.presPinView = true;
+ activeDoc.presPinViewX = x;
+ activeDoc.presPinViewY = y;
+ activeDoc.presPinViewScale = scale;
+ } else if (targetDoc.type === DocumentType.COMPARISON) {
+ const width = targetDoc._clipWidth;
+ activeDoc.presPinClipWidth = width;
+ activeDoc.presPinView = true;
+ }
+ });
}
}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 82dca1287..481b90249 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -4,8 +4,8 @@ $linkGap: 3px;
.documentDecorations-Dark,
.documentDecorations {
- position: absolute;
- z-index: 2000;
+ position: absolute;
+ z-index: 2000;
}
.documentDecorations-Dark {
background: dimgray;
@@ -17,11 +17,11 @@ $linkGap: 3px;
left: 0;
display: grid;
grid-template-rows: 20px 8px 1fr 8px;
- grid-template-columns: 8px 16px 1fr 8px 8px;
+ grid-template-columns: 8px 1fr 8px;
pointer-events: none;
.documentDecorations-centerCont {
- grid-column: 3;
+ grid-column: 2;
background: none;
}
@@ -41,6 +41,7 @@ $linkGap: 3px;
opacity: 1;
transform: translate(10px, 10px);
grid-row: 4;
+ grid-column: 3
}
.documentDecorations-topLeftResizer,
@@ -73,20 +74,18 @@ $linkGap: 3px;
.documentDecorations-topResizer,
.documentDecorations-bottomResizer {
- grid-column-start: 2;
- grid-column-end: 5;
+ grid-column: 2;
}
.documentDecorations-bottomRightResizer,
.documentDecorations-topRightResizer,
.documentDecorations-rightResizer {
- grid-column-start: 5;
- grid-column-end: 7;
+ grid-column: 3;
}
.documentDecorations-rotation,
.documentDecorations-borderRadius {
- grid-column: 5;
+ grid-column: 3;
grid-row: 4;
border-radius: 100%;
background: black;
@@ -132,114 +131,114 @@ $linkGap: 3px;
opacity: 1;
}
- .documentDecorations-topLeftResizer,
- .documentDecorations-leftResizer,
- .documentDecorations-bottomLeftResizer {
- grid-column: 1;
- }
-
- .documentDecorations-topResizer,
- .documentDecorations-bottomResizer {
- grid-column-start: 2;
- grid-column-end: 5;
- }
-
- .documentDecorations-bottomRightResizer,
- .documentDecorations-topRightResizer,
- .documentDecorations-rightResizer {
- grid-column-start: 5;
- grid-column-end: 7;
- }
-
- .documentDecorations-rotation,
- .documentDecorations-borderRadius {
- grid-column: 5;
- grid-row: 4;
- border-radius: 100%;
- background: black;
- height: 8;
- right: -12;
- top: 12;
- position: relative;
- pointer-events: all;
- cursor: nwse-resize;
-
- .borderRadiusTooltip {
- width: 10px;
- height: 10px;
- position: absolute;
- }
- }
- .documentDecorations-rotation {
- background: transparent;
- right: -15;
- }
-
- .documentDecorations-topLeftResizer,
- .documentDecorations-bottomRightResizer {
- cursor: nwse-resize;
- background: unset;
- opacity: 1;
- }
+ .documentDecorations-topLeftResizer,
+ .documentDecorations-leftResizer,
+ .documentDecorations-bottomLeftResizer {
+ grid-column: 1;
+ }
- .documentDecorations-topLeftResizer {
- border-left: 2px solid;
- border-top: solid 2px;
- }
+ .documentDecorations-topResizer,
+ .documentDecorations-bottomResizer {
+ grid-column: 2;
+ }
- .documentDecorations-bottomRightResizer {
- border-right: 2px solid;
- border-bottom: solid 2px;
- }
+ .documentDecorations-bottomRightResizer,
+ .documentDecorations-topRightResizer,
+ .documentDecorations-rightResizer {
+ grid-column: 3
+ }
- .documentDecorations-topLeftResizer:hover,
- .documentDecorations-bottomRightResizer:hover {
- opacity: 1;
- }
+ .documentDecorations-rotation,
+ .documentDecorations-borderRadius {
+ grid-column: 3;
+ grid-row: 4;
+ border-radius: 100%;
+ background: black;
+ height: 8;
+ right: -12;
+ top: 12;
+ position: relative;
+ pointer-events: all;
+ cursor: nwse-resize;
- .documentDecorations-bottomRightResizer {
- grid-row: 4;
- }
+ .borderRadiusTooltip {
+ width: 10px;
+ height: 10px;
+ position: absolute;
+ }
+ }
+ .documentDecorations-rotation {
+ background: transparent;
+ right: -15;
+ }
- .documentDecorations-topRightResizer,
- .documentDecorations-bottomLeftResizer {
- cursor: nesw-resize;
- background: unset;
- opacity: 1;
- }
+ .documentDecorations-topLeftResizer,
+ .documentDecorations-bottomRightResizer {
+ cursor: nwse-resize;
+ background: unset;
+ opacity: 1;
+ }
+
+ .documentDecorations-topLeftResizer {
+ border-left: 2px solid;
+ border-top: solid 2px;
+ }
+
+ .documentDecorations-bottomRightResizer {
+ border-right: 2px solid;
+ border-bottom: solid 2px;
+ }
+
+ .documentDecorations-topLeftResizer:hover,
+ .documentDecorations-bottomRightResizer:hover {
+ opacity: 1;
+ }
+
+ .documentDecorations-bottomRightResizer {
+ grid-row: 4;
+ }
+
+ .documentDecorations-topRightResizer,
+ .documentDecorations-bottomLeftResizer {
+ cursor: nesw-resize;
+ background: unset;
+ opacity: 1;
+ }
- .documentDecorations-topRightResizer {
- border-right: 2px solid;
- border-top: 2px solid;
- }
+ .documentDecorations-topRightResizer {
+ border-right: 2px solid;
+ border-top: 2px solid;
+ }
- .documentDecorations-bottomLeftResizer {
- border-left: 2px solid;
- border-bottom: 2px solid;
- }
+ .documentDecorations-bottomLeftResizer {
+ border-left: 2px solid;
+ border-bottom: 2px solid;
+ }
- .documentDecorations-topRightResizer:hover,
- .documentDecorations-bottomLeftResizer:hover {
+.documentDecorations-topRightResizer:hover,
+.documentDecorations-bottomLeftResizer:hover {
cursor: nesw-resize;
background: black;
opacity: 1;
- }
+}
- .documentDecorations-topResizer,
- .documentDecorations-bottomResizer {
+.documentDecorations-topResizer,
+.documentDecorations-bottomResizer {
cursor: ns-resize;
- }
+}
- .documentDecorations-title-Dark,
- .documentDecorations-title {
+.documentDecorations-title-Dark,
+.documentDecorations-title {
opacity: 1;
- grid-column-start: 2;
- grid-column-end: 4;
+ width: calc(100% - 8px); // = margin-left + margin-right
+ grid-column: 2;
+ grid-column-end: 2;
pointer-events: auto;
overflow: hidden;
text-align: center;
display: flex;
- margin-left: 5px;
+ margin-left: 6px; // closeButton width (14) - leftColumn width (8)
+ margin-right: 2px;
height: 20px;
position: absolute;
border-radius: 8px;
@@ -247,7 +246,7 @@ $linkGap: 3px;
.documentDecorations-titleSpan,
.documentDecorations-titleSpan-Dark {
- width: 100%;
+ width: 100% ;
border-radius: 8px;
background: #ffffffa0;
position: absolute;
@@ -263,105 +262,62 @@ $linkGap: 3px;
background: black;
}
- .documentDecorations-contextMenu {
- width: 25px;
- height: calc(100% + 8px); // 8px for the height of the top resizer bar
- grid-column-start: 2;
- grid-column-end: 2;
- pointer-events: all;
- padding-left: 5px;
- cursor: pointer;
- }
-
- .documentDecorations-titleBackground {
- background: #ffffffcf;
- border-radius: 8px;
- width: 100%;
- height: 100%;
- position: absolute;
- }
-
- .documentDecorations-title {
- opacity: 1;
- grid-column-start: 2;
- grid-column-end: 4;
- pointer-events: auto;
- overflow: hidden;
- text-align: center;
- display: flex;
- margin-left: 5px;
- height: 20px;
- position: absolute;
- .documentDecorations-titleSpan {
- width: 100%;
- border-radius: 8px;
- background: #ffffffcf;
- position: absolute;
- display: inline-block;
- cursor: move;
- }
- }
-
- .focus-visible {
- margin-left: 0px;
- }
-}
+ .documentDecorations-titleBackground {
+ background: #ffffffcf;
+ border-radius: 8px;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ }
-.documentDecorations-iconifyButton {
- opacity: 1;
- grid-column-start: 4;
- grid-column-end: 4;
- pointer-events: all;
- right: 0;
- cursor: pointer;
- position: absolute;
- width: 20px;
+ .focus-visible {
+ margin-left: 0px;
+ }
}
.documentDecorations-openButton {
- display: flex;
- align-items: center;
- opacity: 1;
- grid-column-start: 5;
- grid-column-end: 5;
- pointer-events: all;
- cursor: pointer;
+ display: flex;
+ align-items: center;
+ opacity: 1;
+ grid-column-start: 3;
+ pointer-events: all;
+ cursor: pointer;
}
.documentDecorations-closeButton {
- display: flex;
- align-items: center;
- opacity: 1;
- grid-column-start: 1;
- grid-column-end: 3;
- pointer-events: all;
- cursor: pointer;
-
- > svg {
- margin: 0;
- }
+ display: flex;
+ align-items: center;
+ opacity: 1;
+ grid-column: 1;
+ pointer-events: all;
+ width: 14px;
+ cursor: pointer;
+
+ > svg {
+ margin: 0;
+ }
}
.documentDecorations-background {
- background: lightblue;
- position: absolute;
- opacity: 0.1;
+ background: lightblue;
+ position: absolute;
+ opacity: 0.1;
}
.linkFlyout {
- grid-column: 2/4;
+ grid-column: 2/4;
}
.linkButton-empty:hover {
- background: $medium-gray;
- transform: scale(1.05);
- cursor: pointer;
+ background: $medium-gray;
+ transform: scale(1.05);
+ cursor: pointer;
}
.linkButton-nonempty:hover {
- background: $medium-gray;
- transform: scale(1.05);
- cursor: pointer;
+ background: $medium-gray;
+ transform: scale(1.05);
+ cursor: pointer;
}
.link-button-container {
@@ -379,132 +335,132 @@ $linkGap: 3px;
}
.linkButtonWrapper {
- pointer-events: auto;
- padding-right: 5px;
- width: 25px;
+ pointer-events: auto;
+ padding-right: 5px;
+ width: 25px;
}
.linkButton-linker {
- height: 20px;
- width: 20px;
- text-align: center;
- border-radius: 50%;
- pointer-events: auto;
- color: $dark-gray;
- border: $dark-gray 1px solid;
+ height: 20px;
+ width: 20px;
+ text-align: center;
+ border-radius: 50%;
+ pointer-events: auto;
+ color: $dark-gray;
+ border: $dark-gray 1px solid;
}
.linkButton-linker:hover {
- cursor: pointer;
- transform: scale(1.05);
+ cursor: pointer;
+ transform: scale(1.05);
}
.linkButton-empty,
.linkButton-nonempty {
- height: 20px;
- width: 20px;
- border-radius: 50%;
- opacity: 0.9;
- pointer-events: auto;
- background-color: $dark-gray;
- color: $white;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- transition: transform 0.2s;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
-
- &:hover {
- background: $medium-gray;
- transform: scale(1.05);
- cursor: pointer;
- }
+ height: 20px;
+ width: 20px;
+ border-radius: 50%;
+ opacity: 0.9;
+ pointer-events: auto;
+ background-color: $dark-gray;
+ color: $white;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ background: $medium-gray;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
}
.templating-menu {
- position: absolute;
- pointer-events: auto;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- transition: transform 0.2s;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
+ position: absolute;
+ pointer-events: auto;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
}
.documentdecorations-icon {
- margin: 0px;
+ margin: 0px;
}
.templating-button,
.docDecs-tagButton {
- width: 20px;
- height: 20px;
- border-radius: 50%;
- opacity: 0.9;
- font-size: 14;
- background-color: $dark-gray;
- color: $white;
- text-align: center;
- cursor: pointer;
-
- &:hover {
- background: $medium-gray;
- transform: scale(1.05);
- }
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ opacity: 0.9;
+ font-size: 14;
+ background-color: $dark-gray;
+ color: $white;
+ text-align: center;
+ cursor: pointer;
+
+ &:hover {
+ background: $medium-gray;
+ transform: scale(1.05);
+ }
}
#template-list {
- position: absolute;
- top: 25px;
- left: 0px;
- width: max-content;
- font-family: $sans-serif;
- font-size: 12px;
- background-color: $light-gray;
- padding: 2px 12px;
- list-style: none;
-
- .templateToggle,
- .chromeToggle {
- text-align: left;
- }
-
- input {
- margin-right: 10px;
- }
+ position: absolute;
+ top: 25px;
+ left: 0px;
+ width: max-content;
+ font-family: $sans-serif;
+ font-size: 12px;
+ background-color: $light-gray;
+ padding: 2px 12px;
+ list-style: none;
+
+ .templateToggle,
+ .chromeToggle {
+ text-align: left;
+ }
+
+ input {
+ margin-right: 10px;
+ }
}
@-moz-keyframes spin {
- 100% {
- -moz-transform: rotate(360deg);
- }
+ 100% {
+ -moz-transform: rotate(360deg);
+ }
}
@-webkit-keyframes spin {
- 100% {
- -webkit-transform: rotate(360deg);
- }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ }
}
@keyframes spin {
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
}
@keyframes shadow-pulse {
- 0% {
- box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8);
- }
+ 0% {
+ box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8);
+ }
- 100% {
- box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
- }
+ 100% {
+ box-shadow: 0 0 0 10px rgba(0, 255, 0, 0);
+ }
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d7ead713a..1feb643df 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -144,10 +144,31 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
return true;
}
- onCloseClick = () => {
- const selected = SelectionManager.Views().slice();
- SelectionManager.DeselectAll();
- selected.map(dv => dv.props.removeDocument?.(dv.props.Document));
+ _deleteAfterIconify = false;
+ _iconifyBatch: UndoManager.Batch | undefined;
+ onCloseClick = (force = false) => {
+ if (this.canDelete) {
+ const views = SelectionManager.Views().slice().filter(v => v);
+ this._deleteAfterIconify = force || this._iconifyBatch ? true : false;
+ if (!this._iconifyBatch) {
+ this._iconifyBatch = UndoManager.StartBatch("iconifying");
+ } else {
+ force = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes
+ }
+ var iconifyingCount = views.length;
+ const finished = action((force?: boolean) => {
+ if ((force || --iconifyingCount === 0) && this._iconifyBatch) {
+ if (this._deleteAfterIconify) {
+ views.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document));
+ SelectionManager.DeselectAll();
+ }
+ this._iconifyBatch?.end();
+ this._iconifyBatch = undefined;
+ }
+ });
+ if (force) finished(force);
+ else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished));
+ }
}
onMaximizeDown = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, () => {
@@ -171,7 +192,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
} else if (e.altKey) { // open same document in new tab
CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
} else {
- LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, undefined, selectedDocs.slice(1).map(view => view.props.Document));
+ var openDoc = selectedDocs[0].props.Document;
+ if (openDoc.layoutKey === "layout_icon") {
+ openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc);
+ Doc.deiconifyView(openDoc);
+ }
+ LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document));
}
}
SelectionManager.DeselectAll();
@@ -431,20 +457,28 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
return SelectionManager.Views().length > 1 ? "-multiple-" : "-unset-";
}
+ @computed get canDelete() {
+ return SelectionManager.Views().some(docView => {
+ if (docView.rootDoc.stayInCollection) return false;
+ const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
+ //return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
+ return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
+ });
+ }
+ @computed get hasIcons() {
+ return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === "layout_icon");
+ }
+
render() {
const bounds = this.Bounds;
const seldoc = SelectionManager.Views().slice(-1)[0];
if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
return (null);
}
- const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles;
+ const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup;
const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle;
const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton);
- const canDelete = SelectionManager.Views().some(docView => {
- const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit;
- //return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) &&
- return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin);
- });
+ const canDelete = this.canDelete;
const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => (
<Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top">
<div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()}
@@ -454,16 +488,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
</Tooltip>);
const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme);
- const titleArea = hideTitle ? <div className="documentDecorations-title" onPointerDown={this.onTitleDown} style={{ width: "100%" }} key="title" /> :
+ const titleArea = hideTitle ? <div className="documentDecorations-title" onPointerDown={this.onTitleDown} key="title" /> :
this._edtingTitle ?
<input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`}
- style={{ width: `calc(100% - ${hideResizers ? 0 : 20}px` }}
type="text" name="dynbox" autoComplete="on"
value={this._accumulatedTitle}
onBlur={e => this.titleBlur()}
onChange={action(e => this._accumulatedTitle = e.target.value)}
onKeyPress={this.titleEntered} /> :
- <div className="documentDecorations-title" style={{ width: `calc(100% - ${hideResizers ? 0 : 20}px` }} key="title" onPointerDown={this.onTitleDown} >
+ <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} >
<span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
</div>;
@@ -498,13 +531,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
}}>
- {!canDelete ? <div /> : topBtn("close", "times", undefined, this.onCloseClick, "Close")}
+ {!canDelete ? <div /> : topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, e => this.onCloseClick(this.hasIcons), "Close")}
{titleArea}
{!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")}
{hideResizers ? (null) :
<>
- {SelectionManager.Views().length !== 1 || hideTitle ? (null) :
- topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)}
<div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
<div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
<div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} />
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 4a327a842..6120c4c9e 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -142,11 +142,18 @@ export class KeyManager {
case "delete":
case "backspace":
if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") {
- const selected = SelectionManager.Views().filter(dv => !dv.topMost);
UndoManager.RunInBatch(() => {
- SelectionManager.DeselectAll();
- selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document));
- }, "delete");
+ if (LightboxView.LightboxDoc) {
+ LightboxView.SetLightboxDoc(undefined);
+ SelectionManager.DeselectAll();
+ }
+ else DocumentDecorations.Instance.onCloseClick(true);
+ }, "backspace");
+ // const selected = SelectionManager.Views().filter(dv => !dv.topMost);
+ // UndoManager.RunInBatch(() => {
+ // SelectionManager.DeselectAll();
+ // selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document));
+ // }, "delete");
return { stopPropagation: true, preventDefault: true };
}
break;
@@ -268,7 +275,7 @@ export class KeyManager {
const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2);
const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":");
SelectionManager.Views().length && navigator.clipboard.writeText(text);
- DocumentDecorations.Instance.onCloseClick();
+ DocumentDecorations.Instance.onCloseClick(true);
stopPropagation = false;
preventDefault = false;
}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index e9f63fb5f..c54889157 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -36,7 +36,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
@observable private static _tourMap: Opt<Doc[]> = []; // list of all tours available from the current target
private static _savedState: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, scrollTop: Opt<number> }>;
private static _history: Opt<{ doc: Doc, target?: Doc }[]> = [];
- private static _future: Opt<Doc[]> = [];
+ @observable private static _future: Opt<Doc[]> = [];
private static _docView: Opt<DocumentView>;
private static openInTabFunc: any;
static path: { doc: Opt<Doc>, target: Opt<Doc>, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt<Doc[]>, saved: Opt<{ panX: Opt<number>, panY: Opt<number>, scale: Opt<number>, scrollTop: Opt<number> }> }[] = [];
@@ -64,7 +64,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
}
}
if (future) {
- this._future = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length);
+ this._future = [...(this._future ?? []), ...(this.LightboxDoc ? [this.LightboxDoc] : []), ...future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length),];
}
this._doc = doc;
this._layoutTemplate = layoutTemplate;
@@ -120,7 +120,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
addDocTab = LightboxView.AddDocTab;
@action public static Next() {
const doc = LightboxView._doc!;
- const target = LightboxView._docTarget = LightboxView._future?.pop();
+ const target = LightboxView._docTarget = this._future?.pop();
const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target);
if (targetDocView && target) {
const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement();
@@ -165,11 +165,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
if (docView) {
LightboxView._docTarget = target;
- const focusSpeed = 1000;
- doc._viewTransition = `transform ${focusSpeed}ms`;
if (!target) docView.ComponentView?.shrinkWrap?.();
else docView.focus(target, { willZoom: true, scale: 0.9 });
- setTimeout(() => doc._viewTransition = undefined, focusSpeed);
}
else {
LightboxView.SetLightboxDoc(doc, target);
@@ -197,7 +194,9 @@ export class LightboxView extends React.Component<LightboxViewProps> {
const coll = LightboxView._docTarget;
if (coll) {
const fieldKey = Doc.LayoutFieldKey(coll);
- LightboxView.SetLightboxDoc(coll, undefined, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]);
+ const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])];
+ const links = DocListCast(coll.links).map(link => LinkManager.getOppositeAnchor(link, coll)).filter(doc => doc).map(doc => doc!);
+ LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links);
TabDocView.PinDoc(coll, { hidePresBox: true });
}
}
@@ -232,7 +231,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
const doc = LightboxView._doc;
const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
- else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true });
+ //else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button
}));
})}
Document={LightboxView.LightboxDoc}
@@ -272,7 +271,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
() => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => {
e.stopPropagation();
LightboxView.Next();
- })}
+ }, this.future()?.length.toString())}
<LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} />
<div className="lightboxView-tabBtn" title={"open in tab"}
onClick={e => {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index be26fc561..b66eac564 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -297,6 +297,7 @@ export class MainView extends React.Component {
searchFilterDocs={returnEmptyDoclist}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
+ suppressSetHeight={true}
renderDepth={-1}
/>;
}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index fae385660..43a02d029 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -88,7 +88,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey);
docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])];
showTitle = () => "title";
- setHeightCallback = (height: number) => this.props.setHeight(height + this.filtersHeight());
+ setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight());
render() {
const renderTag = (tag: string) => {
const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 649ee8394..5c10c2344 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -3,10 +3,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
import { action, runInAction } from 'mobx';
+import { extname } from 'path';
import { Doc, Opt, StrListCast } from "../../fields/Doc";
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
-import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
+import { BoolCast, Cast, ImageCast, NumCast, StrCast } from "../../fields/Types";
import { DashColor, lightOrDark } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
import { CurrentUserUtils } from '../util/CurrentUserUtils';
@@ -80,7 +81,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const isAnchor = property.includes(":anchor");
const isAnnotated = property.includes(":annotated");
const isOpen = property.includes(":open");
- const fieldKey = (props as any)?.fieldKey ? (props as any).fieldKey + "-" : isCaption ? "caption-" : "";
+ const fieldKey = props?.fieldKey ? props.fieldKey + "-" : isCaption ? "caption-" : "";
const comicStyle = () => doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === "comic";
const isBackground = () => StrListCast(doc?._layerTags).includes(StyleLayers.Background);
const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
@@ -88,7 +89,15 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + ((Math.abs(x * y) * 9301 + 49297) % 233280 / 233280) * (max - min);
switch (property.split(":")[0]) {
- case StyleProp.TreeViewIcon: return Doc.toIcon(doc, isOpen);
+ case StyleProp.TreeViewIcon:
+ const icon = Cast(doc?.layout_icon, Doc, null) ? Doc.expandTemplateLayout(Cast(doc?.layout_icon, Doc, null), doc) : undefined;
+ if (icon && ImageCast(doc?.icon, ImageCast(doc?.data))) {
+ const img = ImageCast(doc?.icon, ImageCast(doc?.data));
+ const ext = extname(img.url.href);
+ const url = doc?.icon ? img.url.href : img.url.href.replace(ext, "_s" + ext);
+ return <img src={url} width={20} height={15} style={{ margin: "auto", display: "block", objectFit: "contain" }} />;
+ }
+ return Doc.toIcon(doc, isOpen);
case StyleProp.DocContents: return undefined;
case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey";
case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
@@ -98,26 +107,30 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc &&
StrCast(doc._showTitle,
props?.showTitle?.() ||
- (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ?
+ (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ?
(doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) :
"author;creationDate") : "")) || "");
case StyleProp.Color:
if (MainView.Instance.LastButton === doc) return Colors.DARK_GRAY;
const docColor: Opt<string> = StrCast(doc?.[fieldKey + "color"], StrCast(doc?._color));
if (docColor) return docColor;
- const backColor = backgroundCol();
+ const docView = props?.DocumentView?.();
+ const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, "backgroundColor");
if (!backColor) return undefined;
return lightOrDark(backColor);
- case StyleProp.Hidden: return BoolCast(doc?._hidden);
+ case StyleProp.Hidden: return BoolCast(doc?.hidden);
case StyleProp.BorderRounding: return StrCast(doc?.[fieldKey + "borderRounding"], StrCast(doc?.borderRounding, doc?._viewType === CollectionViewType.Pile ? "50%" : ""));
case StyleProp.TitleHeight: return 15;
case StyleProp.BorderPath: return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 };
case StyleProp.JitterRotation: return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
- doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0;
+ doc?.type === DocumentType.RTF || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0;
case StyleProp.BackgroundColor: {
if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY;
- let docColor: Opt<string> = StrCast(doc?.[fieldKey + "backgroundColor"], StrCast(doc?._backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : ""));
+ let docColor: Opt<string> =
+ StrCast(doc?.[fieldKey + "backgroundColor"],
+ StrCast(doc?._backgroundColor,
+ StrCast(props?.Document.backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : "")));
switch (doc?.type) {
case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break;
case DocumentType.PRES: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
@@ -127,7 +140,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break;
case DocumentType.SLIDER: break;
case DocumentType.EQUATION: docColor = docColor || "transparent"; break;
- case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
+ case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined) || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break;
case DocumentType.LINKANCHOR: docColor = isAnchor ? Colors.LIGHT_BLUE : "transparent"; break;
case DocumentType.LINK: docColor = (isAnchor ? docColor : "") || "transparent"; break;
@@ -140,13 +153,16 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case DocumentType.COL:
if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break;
docColor = docColor ||
- (doc?._isGroup ? "#00000004" : // very faint highlight to show bounds of group
- (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc) ? (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY) : // system docs (seen in treeView) get a grayish background
- isBackground() ? "cyan" : // ?? is there a good default for a background collection
- doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc
+ (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc) ? (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY) : // system docs (seen in treeView) get a grayish background
+ isBackground() ? "cyan" : // ?? is there a good default for a background collection
+ doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc
+ (doc?._isGroup ? undefined :
StrCast((props?.renderDepth || 0) > 0 ?
Doc.UserDoc().activeCollectionNestedBackground :
- Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE))));
+ Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ?
+ Colors.BLACK :
+ "linear-gradient(#065fff, #85c1f9)"))
+ ));
break;
//if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)";
default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break;
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 467c2893f..abb4b6bc6 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -44,14 +44,14 @@ export class CollectionCarouselView extends CollectionSubView() {
@computed get content() {
const index = NumCast(this.layoutDoc._itemIndex);
const curDoc = this.childLayoutPairs?.[index];
- const captionProps = { ...this.props, fieldKey: "caption" };
+ const captionProps = { ...OmitKeys(this.props, ["setHeight",]).omit, fieldKey: "caption" };
const marginX = NumCast(this.layoutDoc["caption-xMargin"]);
const marginY = NumCast(this.layoutDoc["caption-yMargin"]);
const showCaptions = StrCast(this.layoutDoc._showCaption);
return !(curDoc?.layout instanceof Doc) ? (null) :
<>
<div className="collectionCarouselView-image" key="image">
- <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit}
+ <DocumentView {...OmitKeys(this.props, ["setHeight", "NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit}
onDoubleClick={this.onContentDoubleClick}
onClick={this.onContentClick}
hideCaptions={showCaptions ? true : false}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index c37fce09c..cec71471b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,7 +11,7 @@ import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { inheritParentAcls } from '../../../fields/util';
-import { emptyFunction } from '../../../Utils';
+import { emptyFunction, incrementTitleCopy } from '../../../Utils';
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
@@ -92,7 +92,7 @@ export class CollectionDockingView extends CollectionSubView() {
DragManager.CompleteWindowDrag = undefined;
}
finishDrag?.(aborted);
- }
+ };
}
@undoBatch
public CloseFullScreen = () => {
@@ -316,7 +316,7 @@ export class CollectionDockingView extends CollectionSubView() {
}
this._goldenLayout.init();
this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped);
- this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart)
+ this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart);
this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged);
}
}
@@ -390,7 +390,6 @@ export class CollectionDockingView extends CollectionSubView() {
}
public static async Copy(doc: Doc, clone = false) {
- clone = !Doc.UserDoc().noviceMode;
let json = StrCast(doc.dockingConfig);
if (clone) {
const cloned = (await Doc.MakeClone(doc));
@@ -403,13 +402,13 @@ export class CollectionDockingView extends CollectionSubView() {
const origtabs = origtabids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc);
const newtabs = origtabs.map(origtab => {
const origtabdocs = DocListCast(origtab.data);
- const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true) : Doc.MakeAlias(origtab);
+ const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeAlias(origtab);
const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeAlias(origtabdoc));
newtabdocs.length && (Doc.GetProto(newtab).data = new List<Doc>(newtabdocs));
json = json.replace(origtab[Id], newtab[Id]);
return newtab;
});
- return Docs.Create.DockDocument(newtabs, json, { title: "Snapshot: " + doc.title });
+ return Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) });
}
@action
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 0ca0a463e..4489601db 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
import { Doc, HeightSym, WidthSym } from "../../../fields/Doc";
import { NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnTrue, setupMoveUpEvents } from "../../../Utils";
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
import { SelectionManager } from "../../util/SelectionManager";
import { SnappingManager } from "../../util/SnappingManager";
@@ -11,6 +11,7 @@ import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormV
import "./CollectionPileView.scss";
import { CollectionSubView } from "./CollectionSubView";
import React = require("react");
+import { ScriptField } from "../../../fields/ScriptField";
@observer
export class CollectionPileView extends CollectionSubView() {
@@ -47,19 +48,20 @@ export class CollectionPileView extends CollectionSubView() {
return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
}
+ toggleIcon = () => {
+ return ScriptField.MakeScript("documentView.iconify()", { documentView: "any" });
+ }
+
// returns the contents of the pileup in a CollectionFreeFormView
@computed get contents() {
const isStarburst = this.layoutEngine() === "starburst";
- const draggingSelf = this.props.isSelected();
return <div className="collectionPileView-innards"
- style={{
- pointerEvents: isStarburst || (SnappingManager.GetIsDragging() && !draggingSelf) ? undefined : "none",
- zIndex: isStarburst && !SnappingManager.GetIsDragging() ? -10 : "auto"
- }} >
+ style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : "none" }} >
<CollectionFreeFormView {...this.props}
layoutEngine={this.layoutEngine}
childDocumentsActive={isStarburst ? returnTrue : undefined}
addDocument={this.addPileDoc}
+ childClickScript={this.toggleIcon()}
moveDocument={this.removePileDoc} />
</div>;
}
@@ -72,13 +74,13 @@ export class CollectionPileView extends CollectionSubView() {
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize);
this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize);
- DocUtils.pileup(this.childDocs, undefined, undefined, false);
+ DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
this.layoutDoc._panX = 0;
this.layoutDoc._panY = -10;
this.props.Document._pileLayoutEngine = 'pass';
} else {
const defaultSize = 25;
- !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500);
+ !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250);
!this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
if (this.layoutEngine() === 'pass') {
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 8634ea139..509005b45 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -141,7 +141,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
() => this.layoutDoc._columnHeaders = new List()
);
this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight,
- autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
+ autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
this.headerMargin + (this.isStackingView ?
Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) :
this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0)))));
@@ -408,7 +408,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER),
Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))));
if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- this.props.setHeight(height);
+ this.props.setHeight?.(height);
}
}
}));
@@ -458,7 +458,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this.observer = new _global.ResizeObserver(action((entries: any) => {
if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) {
const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0);
- this.props.setHeight(this.headerMargin + height);
+ this.props.setHeight?.(this.headerMargin + height);
}
}));
this.observer.observe(ref);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d8f1287cd..30e0adf24 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,7 +1,7 @@
import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx";
import CursorField from "../../../fields/CursorField";
import { Doc, Opt, Field, DocListCast, AclPrivate, StrListCast } from "../../../fields/Doc";
-import { Id, ToString } from "../../../fields/FieldSymbols";
+import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
@@ -453,7 +453,7 @@ export function CollectionSubView<X>(moreProps?: X) {
if (completed) completed(set);
else {
if (isFreeformView && generatedDocuments.length > 1) {
- addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+ addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!,);
} else {
generatedDocuments.forEach(addDocument);
}
@@ -480,5 +480,4 @@ import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTex
import { CollectionView, CollectionViewType, CollectionViewProps } from "./CollectionView";
import { SelectionManager } from "../../util/SelectionManager";
import { OverlayView } from "../OverlayView";
-import { GetEffectiveAcl, TraceMobx } from "../../../fields/util";
-
+import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index d6398fda5..e63ea7151 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -235,6 +235,7 @@ export class CollectionTimeView extends CollectionSubView() {
ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
+ const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField;
@@ -249,5 +250,13 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
pivotDoc._pivotField = filterVals[0];
}
}
+ const newFilters = StrListCast(pivotDoc._docFilters);
+ if (newFilters.length && originalFilter.length &&
+ newFilters[newFilters.length - 1] === originalFilter[originalFilter.length - 1]) {
+ pivotDoc._prevFilterIndex = --prevFilterIndex;
+ pivotDoc["_prevDocFilter" + prevFilterIndex] = undefined;
+ pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = undefined;
+ pivotDoc["_prevPivotFields" + prevFilterIndex] = undefined;
+ }
});
}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index b664d9d82..a8eee9c19 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -2,6 +2,7 @@
.collectionTreeView-container {
transform-origin: top left;
+ height: 100%;
}
.collectionTreeView-dropTarget {
border-width: $COLLECTION_BORDER_WIDTH;
@@ -71,6 +72,11 @@
display: none;
}
+.collectionTreeView-contents {
+ display: flex;
+ flex-direction: column;
+}
+
.collectionTreeView-titleBar {
display: inline-block;
width: 100%;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e84517f40..41970eb96 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -88,7 +88,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace("px", ""));
const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot());
this.layoutDoc._autoHeightMargins = bodyHeight;
- this.props.setHeight(bodyHeight + titleHeight);
+ this.props.setHeight?.(bodyHeight + titleHeight);
}
}
unobserveHeight = (ref: any) => {
@@ -341,14 +341,13 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
<div className="collectionTreeView-contents" key="tree" style={{
...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
overflow: "auto",
- height: this.layoutDoc._autoHeight ? "max-content" : "100%"
+ height: "100%"//this.layoutDoc._autoHeight ? "max-content" : "100%"
}} >
{titleBar}
<div className="collectionTreeView-container"
style={{
transform: this.outlineMode ? `scale(${this.contentScaling})` : "",
paddingLeft: `${this.marginX()}px`,
- height: "max-content",
width: this.outlineMode ? `calc(${100 / this.contentScaling}%)` : ""
}}
onContextMenu={this.onContextMenu}>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index f677a2f6c..07cc543d8 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -33,6 +33,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
import { CollectionView, CollectionViewType } from './CollectionView';
import "./TabDocView.scss";
import React = require("react");
+import { List } from '../../../fields/List';
const _global = (window /* browser */ || global /* node */) as any;
interface TabDocViewProps {
@@ -209,9 +210,18 @@ export class TabDocView extends React.Component<TabDocViewProps> {
const pinDoc = Doc.MakeAlias(doc);
pinDoc.presentationTargetDoc = doc;
pinDoc.title = doc.title + " - Slide";
+ pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
pinDoc.presMovement = PresMovement.Zoom;
pinDoc.groupWithUp = false;
pinDoc.context = curPres;
+ // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time
+ pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area
+ pinDoc.treeViewHeaderWidth = "100%"; // forces the header to grow to be the same size as its largest sibling.
+ pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
+ pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
+ pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
+ pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header
+ pinDoc.treeViewHideHeader = true; // this will force the document to render itself as the tree view header
const presArray: Doc[] = PresBox.Instance?.sortArray();
const size: number = PresBox.Instance?._selectedArray.size;
const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 2e33d3564..7c75810d3 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -47,7 +47,7 @@
width: $TREE_BULLET_WIDTH;
color: $medium-gray;
margin-top: 3px;
- transform: scale(1.3, 1.3);
+ // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder.
border: #80808030 1px solid;
border-radius: 4px;
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 751ae631b..f046538c6 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -1,7 +1,8 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym, StrListCast } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { RichTextField } from '../../../fields/RichTextField';
@@ -9,7 +10,7 @@ import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, simulateMouseClick, Utils, returnOne } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, simulateMouseClick, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from "../../documents/DocumentTypes";
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
@@ -21,18 +22,15 @@ import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { EditableView } from "../EditableView";
import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
-import { DocumentView, DocumentViewProps, StyleProviderFunc, DocumentViewInternal } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView';
import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
-import { KeyValueBox } from '../nodes/KeyValueBox';
-import { SliderBox } from '../nodes/SliderBox';
-import { StyleProp, testDocProps } from '../StyleProvider';
+import { StyleProp } from '../StyleProvider';
import { CollectionTreeView } from './CollectionTreeView';
import { CollectionView, CollectionViewType } from './CollectionView';
import "./TreeView.scss";
import React = require("react");
-import { PresMovement } from '../nodes/trails/PresEnums';
-import { PresBox } from '../nodes/trails';
+import { KeyValueBox } from '../nodes/KeyValueBox';
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -111,11 +109,10 @@ export class TreeView extends React.Component<TreeViewProps> {
@computed get treeViewOpen() { return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._transientOpenState; }
@computed get treeViewExpandedView() { return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containerCollection.maxEmbedHeight, 200); }
- @computed get dataDoc() { return this.doc[DataSym]; }
+ @computed get dataDoc() { return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym]; }
@computed get layoutDoc() { return Doc.Layout(this.doc); }
- @computed get fieldKey() { return Doc.LayoutFieldKey(this.doc); }
- // @computed get childDocs() { return this.childDocList(this.fieldKey); }
- @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
+ @computed get fieldKey() { return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc)); }
+ @computed get childDocs() { return this.childDocList(this.fieldKey); }
@computed get childLinks() { return this.childDocList("links"); }
@computed get childAliases() { return this.childDocList("aliases"); }
@computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); }
@@ -168,18 +165,7 @@ export class TreeView extends React.Component<TreeViewProps> {
docView.select(false);
}
}
- @action
- openLevel = (docView: DocumentView) => {
- if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) {
- this.treeViewOpen = !this.treeViewOpen;
- } else {
- // choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias
- // this.props.addDocTab(CurrentUserUtils.ActiveDashboard.isShared ? Doc.MakeAlias(this.props.document) : this.props.document, "add:right");
- const bestAlias = DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
- this.props.addDocTab(bestAlias ?? Doc.MakeAlias(this.props.document), "add:right");
- }
- }
constructor(props: any) {
super(props);
if (!TreeView._openLevelScript) {
@@ -248,7 +234,7 @@ export class TreeView extends React.Component<TreeViewProps> {
title: "-title-",
treeViewExpandedViewLock: true, treeViewExpandedView: "data",
_viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline",
- x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, backgroundColor: "transparent", _width: 1000, _height: 10
+ x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _width: 1000, _height: 10
});
Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text');
Doc.GetProto(bullet).data = new List<Doc>([]);
@@ -303,7 +289,12 @@ export class TreeView extends React.Component<TreeViewProps> {
(doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean);
const move = (!dropAction || dropAction === "proto" || dropAction === "move" || dropAction === "same") && moveDocument;
if (canAdd) {
- return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false));
+ return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) =>
+ (move ?
+ move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false)
+ :
+ addDoc(d)) || added,
+ false));
}
return false;
}
@@ -391,8 +382,14 @@ export class TreeView extends React.Component<TreeViewProps> {
return rows;
}
- rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1);
- rtfHeight = () => this.rtfWidth() <= this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+ rtfWidth = () => {
+ const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+ return Math.min(layout[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1);
+ }
+ rtfHeight = () => {
+ const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+ return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+ }
rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth());
expandPanelHeight = () => {
if (this.layoutDoc._fitWidth) return this.docHeight();
@@ -484,7 +481,7 @@ export class TreeView extends React.Component<TreeViewProps> {
onClick={this.bulletClick}
style={this.props.treeView.outlineMode ? { opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) } : {
color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"),
- opacity: checked === "unchecked" ? undefined : 0.4
+ opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4
}}>
{this.props.treeView.outlineMode ?
!(this.doc.text as RichTextField)?.Text ? (null) :
@@ -497,7 +494,7 @@ export class TreeView extends React.Component<TreeViewProps> {
checked === "unchecked" ? "square" :
!this.treeViewOpen ? "caret-right" : "caret-down"} />
</div>
- {this.onCheckedClick ? (null) : <FontAwesomeIcon icon={iconType} />}
+ {this.onCheckedClick ? (null) : typeof iconType === "string" ? <FontAwesomeIcon icon={iconType as IconProp} /> : iconType}
</div>
}
</div>;
@@ -665,13 +662,20 @@ export class TreeView extends React.Component<TreeViewProps> {
}
onKeyDown = (e: React.KeyboardEvent) => {
if (this.doc.treeViewHideHeader || this.props.treeView.outlineMode) {
- e.stopPropagation();
- e.preventDefault();
switch (e.key) {
- case "Tab": setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
+ case "Tab":
+ e.stopPropagation();
+ e.preventDefault();
+ setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150);
return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab");
- case "Backspace": return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc);
- case "Enter": return UndoManager.RunInBatch(this.makeTextCollection, "bullet");
+ case "Backspace":
+ e.stopPropagation();
+ e.preventDefault();
+ return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc);
+ case "Enter":
+ e.stopPropagation();
+ e.preventDefault();
+ return UndoManager.RunInBatch(this.makeTextCollection, "bullet");
}
}
}
@@ -776,7 +780,7 @@ export class TreeView extends React.Component<TreeViewProps> {
renderBulletHeader = (contents: JSX.Element, editing: boolean) => {
return <>
<div className={`treeView-header` + (editing ? "-editing" : "")} key="titleheader"
- style={{ width: "max-content" }}
+ style={{ width: StrCast(this.doc.treeViewHeaderWidth, "max-content") }}
ref={this._header}
onClick={this.ignoreEvent}
onPointerDown={this.ignoreEvent}
@@ -790,8 +794,7 @@ export class TreeView extends React.Component<TreeViewProps> {
renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
- const layout = StrCast(Doc.LayoutField(this.layoutDoc));
- const isExpandable = layout.includes(FormattedTextBox.name) || layout.includes(SliderBox.name);
+ const isExpandable = this.doc._treeViewGrowsHorizontally;
const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth;
const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight;
return <DocumentView key={this.doc[Id]} ref={action((r: DocumentView | null) => this._dref = r)}
@@ -802,6 +805,7 @@ export class TreeView extends React.Component<TreeViewProps> {
NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined}
NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined}
LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined}
+ LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
isContentActive={isActive}
isDocumentActive={isActive}
styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
@@ -844,10 +848,10 @@ export class TreeView extends React.Component<TreeViewProps> {
}
// renders the document in the header field instead of a text proxy.
- @computed get renderDocumentAsHeader() {
+ renderDocumentAsHeader = (asText: boolean) => {
return <>
{this.renderBullet}
- {this.renderEmbeddedDocument(true, this.props.isContentActive)}
+ {this.renderEmbeddedDocument(asText, this.props.isContentActive)}
</>;
}
@@ -879,9 +883,9 @@ export class TreeView extends React.Component<TreeViewProps> {
//onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document
onKeyDown={this.onKeyDown}>
<li className="collection-child">
- {hideTitle && this.doc.type !== DocumentType.RTF ?
+ {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader"
this.renderEmbeddedDocument(false, returnFalse) :
- this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader : this.renderTitleAsHeader, this._editTitle)}
+ this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
</li>
</div>;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 9fed82dae..16c7df311 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -111,23 +111,25 @@ export function computerStarburstLayout(
viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
engineProps: any
) {
+ const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
const docMap = new Map<string, PoolData>();
- const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
- const docScale = NumCast(pivotDoc._starburstDocScale);
- const docSize = docScale * 100; // assume a icon sized at 100
- const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
+ const docSize = mustFit ? panelDim[0] * .33 : 75; // assume an icon sized at 75
+ const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize];
+ const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize];
childPairs.forEach(({ layout, data }, i) => {
+ const docSize = layout.layoutKey === "layout_icon" ? (mustFit ? panelDim[0] * .33 : 75) : 400; // assume a icon sized at 75
const deg = i / childPairs.length * Math.PI * 2;
docMap.set(layout[Id], {
- x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2,
- y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2,
- width: docScale * layout[WidthSym](),
- height: docScale * layout[HeightSym](),
+ x: Math.cos(deg) * burstRadius[0] - docSize / 2,
+ y: Math.sin(deg) * burstRadius[1] - docSize * layout[HeightSym]() / layout[WidthSym]() / 2,
+ width: docSize,//layout[WidthSym](),
+ height: docSize * layout[HeightSym]() / layout[WidthSym](),
+ zIndex: NumCast(layout.zIndex),
pair: { layout, data },
replica: ""
});
});
- const divider = { type: "div", color: "transparent", x: -burstRadius[0] / 3, y: 0, width: 15, height: 15, payload: undefined };
+ const divider = { type: "div", color: "transparent", x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
}
@@ -265,7 +267,13 @@ export function computePivotLayout(
});
const dividers = sortedPivotKeys.map((key, i) =>
- ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
+ ({
+ type: "div", color: "lightGray",
+ x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2,
+ y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander,
+ height: maxColHeight,
+ payload: pivotColumnGroups.get(key)!.filters
+ }));
groupNames.push(...dividers);
return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
}
@@ -402,7 +410,7 @@ function normalizeResults(
const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
const aggBounds = aggregateBounds(extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" })))).filter(e => e.zIndex !== -99), 0, 0);
- aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
+ aggBounds.r = aggBounds.x + Math.max(minWidth, aggBounds.r - aggBounds.x);
const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
if (Number.isNaN(scale)) scale = 1;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b2697cf08..bdae5a4f8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,6 +9,7 @@ import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fiel
import { List } from "../../../../fields/List";
import { ObjectField } from "../../../../fields/ObjectField";
import { RichTextField } from "../../../../fields/RichTextField";
+import { ImageField } from "../../../../fields/URLField";
import { createSchema, listSpec } from "../../../../fields/Schema";
import { ScriptField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
@@ -41,6 +42,8 @@ import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDo
import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { PresBox } from "../../nodes/trails/PresBox";
+import { VideoBox } from "../../nodes/VideoBox";
+import { CreateImage } from "../../nodes/WebBoxRenderer";
import { StyleLayers, StyleProp } from "../../StyleProvider";
import { CollectionDockingView } from "../CollectionDockingView";
import { CollectionSubView } from "../CollectionSubView";
@@ -451,9 +454,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.preventDefault();
break;
case InkTool.None:
- this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
+ if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
+ this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
break;
}
}
@@ -1011,8 +1016,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
} else if (doc.isInkMask) {
doc.zIndex = 5000;
} else {
- const docs = this.childLayoutPairs.map(pair => pair.layout);
- docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
+ docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1;
if (zlast - docs.length > 100) {
for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
@@ -1058,11 +1063,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.props.focus(doc, options);
} else {
const xfToCollection = options?.docTransform ?? Transform.Identity();
- const layoutdoc = Doc.Layout(doc);
const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: this.Document[this.scaleFieldKey] };
const newState = HistoryUtil.getState();
const cantTransform = this.props.isAnnotationOverlay || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
- const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(layoutdoc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
+ const { panX, panY, scale } = cantTransform ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willZoom ? options?.scale || .75 : undefined);
if (!cantTransform) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
newState.initializers![this.Document[Id]] = { panX: panX, panY: panY };
HistoryUtil.pushState(newState);
@@ -1109,10 +1113,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
+ const layoutdoc = Doc.Layout(doc);
const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1);
const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1);
const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
- const pt2 = xf.transformPoint(NumCast(doc.x) + doc[WidthSym](), NumCast(doc.y) + doc[HeightSym]());
+ const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] };
const cx = NumCast(this.layoutDoc._panX);
const cy = NumCast(this.layoutDoc._panY);
@@ -1142,7 +1147,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
- getChildDocView(entry: PoolData, renderIndex: number) {
+ getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
@@ -1151,8 +1156,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
Document={childLayout}
renderDepth={this.props.renderDepth + 1}
replica={entry.replica}
- renderIndex={renderIndex}
dataTransition={entry.transition}
+ suppressSetHeight={this.layoutEngine ? true : false}
renderCutoffProvider={this.renderCutoffProvider}
ContainingCollectionView={this.props.CollectionView}
ContainingCollectionDoc={this.props.Document}
@@ -1295,10 +1300,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return [] as ViewDefResult[];
}
+ @computed get layoutEngine() { return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); }
@computed get doInternalLayoutComputation() {
TraceMobx();
const newPool = new Map<string, PoolData>();
- switch (this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine)) {
+ switch (this.layoutEngine) {
case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
@@ -1328,7 +1334,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const elements = computedElementData.slice();
Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach((entry, i) =>
elements.push({
- ele: this.getChildDocView(entry[1], i),
+ ele: this.getChildDocView(entry[1]),
bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
}));
@@ -1411,6 +1417,37 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}));
}
+ updateIcon = () => {
+ this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString();
+ this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString();
+ const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!);
+ this.props.docViewPath().lastElement().ContentDiv!.style.width = "";
+ this.props.docViewPath().lastElement().ContentDiv!.style.height = "";
+ const nativeWidth = this.layoutDoc[WidthSym]();
+ const nativeHeight = this.layoutDoc[HeightSym]();
+
+ CreateImage(
+ "",
+ document.styleSheets,
+ htmlString?.replace(/docView-hack/g, 'documentView-hack'),
+ nativeWidth,
+ nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
+ NumCast(this.layoutDoc._scrollTop)
+ ).then
+ ((data_url: any) => {
+ VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
+ returnedfilename => setTimeout(action(() => {
+
+ this.dataDoc.icon = new ImageField(returnedfilename);
+ this.dataDoc["icon-nativeWidth"] = nativeWidth;
+ this.dataDoc["icon-nativeHeight"] = nativeHeight;
+ }), 500));
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
+ }
+
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
@@ -1476,6 +1513,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
!Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+ appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
this.props.ContainingCollectionView &&
appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" });
!Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 62510ce9d..41e4d6b6a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -10,6 +10,7 @@
user-select: none;
}
+
.marqueeView:focus-within {
overflow: hidden;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e10273523..afdb91633 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -562,15 +562,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
d.y = NumCast(d.y) - this.Bounds.top;
return d;
});
- const summary = Docs.Create.TextDocument("", { x: this.Bounds.left, y: this.Bounds.top, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
- const portal = Doc.MakeAlias(summary);
- Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected);
- Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations");
- summary._backgroundColor = "#e2ad32";
- portal.layoutKey = "layout_portal";
- portal.title = "document collection";
+ const summary = Docs.Create.TextDocument("", { _backgroundColor: "#e2ad32", x: this.Bounds.left, y: this.Bounds.top, isPushpin: true, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
+ const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: "transparent" });
DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", "");
+ portal.hidden = true;
+ this.props.addDocument?.(portal);
this.props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
index a6171af51..845b07c51 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.scss
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -13,6 +13,10 @@
display: flex;
flex-direction: row;
+ .document-wrapper:hover {
+ z-index: 2000;
+ }
+
.react-grid-layout {
width: 100%;
}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 58ea7410d..da102fe18 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -166,7 +166,7 @@ export class CollectionGridView extends CollectionSubView() {
ScreenToLocalTransform={dxf}
onClick={this.onChildClickHandler}
renderDepth={this.props.renderDepth + 1}
- dontCenter={"y"}
+ dontCenter={this.props.Document.centerY ? "" : "y"}
/>;
}
@@ -283,6 +283,7 @@ export class CollectionGridView extends CollectionSubView() {
onContextMenu = () => {
const displayOptionsMenu: ContextMenuProps[] = [];
displayOptionsMenu.push({ description: "Toggle Content Display Style", event: () => this.props.Document.display = this.props.Document.display ? undefined : "contents", icon: "copy" });
+ displayOptionsMenu.push({ description: "Toggle Vertical Centering", event: () => this.props.Document.centerY = !this.props.Document.centerY, icon: "copy" });
ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
}
@@ -293,7 +294,7 @@ export class CollectionGridView extends CollectionSubView() {
if (this.props.isContentActive(true)) {
setupMoveUpEvents(this, e, returnFalse, returnFalse,
(e: PointerEvent, doubleTap?: boolean) => {
- if (doubleTap) {
+ if (doubleTap && !e.button) {
undoBatch(action(() => {
const text = Docs.Create.TextDocument("", { _width: 150, _height: 50 });
FormattedTextBox.SelectOnLoad = text[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5149d370f..3f16d3c49 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
import { observer } from "mobx-react";
import { Doc, Opt } from "../../../fields/Doc";
import { List } from "../../../fields/List";
@@ -17,7 +17,6 @@ import { StyleProp } from "../StyleProvider";
import "./CollectionFreeFormDocumentView.scss";
import { DocumentView, DocumentViewProps } from "./DocumentView";
import React = require("react");
-import { DocumentType } from "../../documents/DocumentTypes";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined;
@@ -29,7 +28,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
jitterRotation: number;
dataTransition?: string;
replica: string;
- renderIndex: number;
CollectionFreeFormView: CollectionFreeFormView;
}
@@ -167,16 +165,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
PanelHeight: this.panelHeight,
};
const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && DashColor(background).alpha() !== 1 ? "multiply" : undefined);
+ const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && (!background.startsWith("linear") && DashColor(background).alpha() !== 1) ? "multiply" : undefined);
return <div className={"collectionFreeFormDocumentView-container"}
style={{
outline: this.Highlight ? "orange solid 2px" : "",
width: this.panelWidth(),
- height: !this.ShowTitle && this.layoutDoc.autoHeight && this.rootDoc.type === DocumentType.RTF ? undefined : this.panelHeight(),
+ height: this.panelHeight(),
transform: this.transform,
transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
zIndex: this.ZInd,
- mixBlendMode,
+ mixBlendMode: mixBlendMode,
display: this.ZInd === -99 ? "none" : undefined
}} >
{this.props.renderCutoffProvider(this.props.Document) ?
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 005133eb0..d4e8ffc7f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -112,7 +112,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
isSelected: (outsideReaction: boolean) => boolean,
select: (ctrl: boolean) => void,
scaling?: () => number,
- setHeight: (height: number) => void,
+ setHeight?: (height: number) => void,
layoutKey: string,
}> {
@computed get layout(): string {
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 433a0bf48..a9c998757 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -1,3 +1,4 @@
+
import { observer } from "mobx-react";
import * as React from "react";
import { DocumentView } from "./DocumentView";
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 6d96a9ab6..46537df7e 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -278,7 +278,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
//render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu
return (!Array.from(this.filteredLinks).length && !this.props.AlwaysOn) ? (null) :
- <div className="documentLinksButton-wrapper" >
+ <div className="documentLinksButton-wrapper" style={{ transform: this.props.InMenu ? undefined : `scale(${(this.props.ContentScaling?.() || 1) * this.props.View.screenToLocalTransform().Scale})` }} >
{
(this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) ||
(!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ?
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 4565f8504..5c5d2df10 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -3,6 +3,9 @@
.documentView-effectsWrapper {
border-radius: inherit;
}
+.documentView-hack {
+ display: inline;
+}
.documentView-customBorder {
width:100%;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7361dba73..f5a9e3b3e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -80,6 +80,7 @@ export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>
export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
export interface DocComponentView {
+ updateIcon?: () => void; // updates the icon representation of the document
getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
@@ -103,13 +104,17 @@ export interface DocComponentView {
snapPt?: (pt: { X: number, Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number, Y: number }, distance: number };
search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
}
+// These props are passed to both FieldViews and DocumentViews
export interface DocumentViewSharedProps {
+ fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps. In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
+ DocumentView?: () => DocumentView;
renderDepth: number;
Document: Doc;
DataDoc?: Doc;
fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
+ suppressSetHeight?: boolean;
thumbShown?: () => boolean;
isHovering?: () => boolean;
setContentView?: (view: DocComponentView) => any;
@@ -148,6 +153,8 @@ export interface DocumentViewSharedProps {
createNewFilterDoc?: () => void;
updateFilterDoc?: (doc: Doc) => void;
}
+
+// these props are specific to DocuentViews
export interface DocumentViewProps extends DocumentViewSharedProps {
// properties specific to DocumentViews but not to FieldView
freezeDimensions?: boolean;
@@ -172,6 +179,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
onPointerUp?: () => ScriptField;
}
+// these props are only available in DocumentViewIntenral
export interface DocumentViewInternalProps extends DocumentViewProps {
NativeWidth: () => number;
NativeHeight: () => number;
@@ -210,7 +218,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
@computed get ContentScale() { return this.props.ContentScaling?.() || 1; }
@computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); }
- @computed get hidden() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Hidden); }
+ @computed get hidden() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); }
@computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); }
@computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); }
@computed get borderRounding() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); }
@@ -451,7 +459,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
});
// after a timeout, the right _componentView should have been created, so call it to update its view spec values
setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
- const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+ const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
...options, afterFocus: (didFocus: boolean) =>
@@ -593,8 +601,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@undoBatch @action
toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
this.Document.ignoreClick = false;
- this.Document._isLinkButton = !this.Document._isLinkButton;
- setPushpin && (this.Document.isPushpin = this.Document._isLinkButton);
+ if (setPushpin) {
+ this.Document.isPushpin = !this.Document.isPushpin;
+ this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton;
+ } else {
+ this.Document._isLinkButton = !this.Document._isLinkButton;
+ }
if (this.Document._isLinkButton && !this.onClickHandler) {
this.Document.followLinkZoom = zoom;
this.Document.followLinkLocation = location;
@@ -751,7 +763,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
}
- !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" });
+ !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" });
!Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
!Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
@@ -831,11 +843,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
contentScaling = () => this.ContentScale;
onClickFunc = () => this.onClickHandler;
- setHeight = (height: number) => {
- if (this.props.renderDepth !== -1) {
- this.layoutDoc._height = height;
- }
- }
+ setHeight = (height: number) => this.layoutDoc._height = height;
setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
isContentActive = (outsideReaction?: boolean) => {
return this.props.isContentActive() === false ? false : (
@@ -858,9 +866,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }}
icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
</div>;
+
return <div className="documentView-contentsView"
style={{
- pointerEvents: this.props.pointerEvents as any ? this.props.pointerEvents as any : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"),
+ pointerEvents: this.props.pointerEvents as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"),
height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
{!this._retryThumb || !this.thumbShown() ? (null) :
@@ -878,7 +887,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
setContentView={this.setContentView}
scaling={this.contentScaling}
PanelHeight={this.panelHeight}
- setHeight={this.setHeight}
+ setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
isContentActive={this.isContentActive}
ScreenToLocalTransform={this.screenToLocal}
rootSelected={this.rootSelected}
@@ -886,10 +895,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
focus={this.focus}
layoutKey={this.finalLayoutKey} />
{this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
- {!this.props.isSelected() || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) :
+ {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) :
<DocumentLinksButton View={this.props.DocumentView()}
ContentScaling={this.props.ContentScaling}
- Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />
+ Offset={[this.topMost ? 0 : !this.props.isSelected() ? - 15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? - 15 : -30]} />
}
{audioView}
</div>;
@@ -1029,7 +1038,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
/>
</div>;
const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
- const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
+ const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor,
+ Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
const titleView = !showTitle ? (null) :
<div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
position: this.headerMargin ? "relative" : "absolute",
@@ -1110,7 +1120,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
// Return surrounding highlight
- return <div className={DocumentView.ROOT_DIV} ref={this._mainCont}
+ return <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont}
onContextMenu={this.onContextMenu}
onKeyDown={this.onKeyDown}
onPointerDown={this.onPointerDown}
@@ -1118,6 +1128,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onPointerEnter={e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)}
onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)}
style={{
+ display: this.hidden ? "inline" : undefined,
borderRadius: this.borderRounding,
pointerEvents: this.pointerEvents,
outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px",
@@ -1193,7 +1204,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
}
- @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); }
+ @computed get shouldNotScale() {
+ return (this.fitWidth && !this.nativeWidth) ||
+ this.props.ContainingCollectionView?.collectionViewType === CollectionViewType.Time ||
+ this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
+ }
@computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
@computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
@computed get nativeScaling() {
@@ -1232,15 +1247,17 @@ export class DocumentView extends React.Component<DocumentViewProps> {
return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
}
- public iconify() {
+ public iconify(finished?: () => void) {
+ this.ComponentView?.updateIcon?.();
const layoutKey = Cast(this.Document.layoutKey, "string", null);
if (layoutKey !== "layout_icon") {
- this.switchViews(true, "icon");
+ this.switchViews(true, "icon", finished);
if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", "");
} else {
const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null);
- this.switchViews(deiconifyLayout ? true : false, deiconifyLayout);
+ this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished);
this.Document.deiconifyLayout = undefined;
+ this.props.bringToFront(this.rootDoc);
}
}
@undoBatch
@@ -1249,12 +1266,15 @@ export class DocumentView extends React.Component<DocumentViewProps> {
Doc.setNativeView(this.props.Document);
custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
}
- switchViews = action((custom: boolean, view: string) => {
+ switchViews = action((custom: boolean, view: string, finished?: () => void) => {
this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
setTimeout(action(() => {
this.setCustomView(custom, view);
this.docView && (this.docView._animateScalingTo = 1); // expand it
- setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), this.docView!._animateScaleTime - 10);
+ setTimeout(action(() => {
+ this.docView && (this.docView._animateScalingTo = 0);
+ finished?.();
+ }), this.docView!._animateScaleTime - 10);
}), this.docView!._animateScaleTime - 10);
});
@@ -1295,6 +1315,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
TraceMobx();
const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+ const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
return (<div className="contentFittingDocumentView">
{!this.props.Document || !this.props.PanelWidth() ? (null) : (
@@ -1303,8 +1324,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
transition: this.props.dataTransition,
position: this.props.Document.isInkMask ? "absolute" : undefined,
transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
- width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
- height: (!this.props.ignoreAutoHeight && this.layoutDoc.autoHeight && this.layoutDoc.type === DocumentType.RTF) || isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
+ width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
+ height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
`${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
}}>
<DocumentViewInternal {...this.props}
@@ -1319,7 +1340,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
ContentScaling={this.ContentScale}
ScreenToLocalTransform={this.screenToLocalTransform}
focus={this.props.focus || emptyFunction}
- bringToFront={emptyFunction}
ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
</div>)}
</div>);
@@ -1332,8 +1352,8 @@ export function deiconifyViewFunc(documentView: DocumentView) {
}
ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
documentView.iconify();
-}
-);
+ documentView.select(false);
+});
ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 943b9f153..ae9acfe3f 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -22,7 +22,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
isDocumentActive?: () => boolean;
isSelected: (outsideReaction?: boolean) => boolean;
scaling?: () => number;
- setHeight: (height: number) => void;
+ setHeight?: (height: number) => void;
// properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
pointerEvents?: string;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 17f95c1cc..a1de944d8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -73,7 +73,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0],
selected: this.props.isSelected()
}),
- ({ forceFull, scrSize, selected }) => this._curSuffix = forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o",
+ ({ forceFull, scrSize, selected }) => this._curSuffix = this.fieldKey === "icon" ? "_m" : forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o",
{ fireImmediately: true, delay: 1000 });
this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
({ nativeSize, width }) => {
@@ -245,8 +245,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get nativeSize() {
TraceMobx();
- const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 500);
- const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1);
+ const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"], 500));
+ const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"], 1));
const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + "-nativeOrientation"], 1);
return { nativeWidth, nativeHeight, nativeOrientation };
}
@@ -300,17 +300,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
</div>;
}
- // adjust y position to center image in panel aspect is bigger than image aspect.
- // bcz :note, this is broken for rotated images
- get ycenter() {
- const { nativeWidth, nativeHeight } = this.nativeSize;
- const rotation = NumCast(this.dataDoc[this.fieldKey + "-rotation"]);
- const aspect = (rotation % 180) ? nativeWidth / nativeHeight : nativeHeight / nativeWidth;
- return this.props.PanelHeight() / this.props.PanelWidth() > aspect ?
- (this.props.PanelHeight() - this.props.PanelWidth() * aspect) / 2 : 0;
- }
-
- screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.ycenter);
+ screenToLocalTransform = this.props.ScreenToLocalTransform;
contentFunc = () => [this.content];
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index c44c8c828..4b1fbaf7d 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -67,7 +67,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean {
const { script, type, onDelegate } = kvpScript;
//const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
- const target = forceOnDelegate || onDelegate ? doc : doc.proto || doc;
+ const target = forceOnDelegate || onDelegate || key.startsWith("_") ? doc : doc.proto || doc;
let field: Field;
if (type === "computed") {
field = new ComputedField(script);
diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js
index db43551d8..02c36c4bc 100644
--- a/src/client/views/nodes/LabelBigText.js
+++ b/src/client/views/nodes/LabelBigText.js
@@ -5,14 +5,14 @@ And from Jetroid/bigtext.js v1.0.0, September 2016
Usage:
BigText("#myElement",{
- rotateText: {Number}, (null)
- fontSizeFactor: {Number}, (0.8)
- maximumFontSize: {Number}, (null)
- limitingDimension: {String}, ("both")
- horizontalAlign: {String}, ("center")
- verticalAlign: {String}, ("center")
- textAlign: {String}, ("center")
- whiteSpace: {String}, ("nowrap")
+ rotateText: {Number}, (null)
+ fontSizeFactor: {Number}, (0.8)
+ maximumFontSize: {Number}, (null)
+ limitingDimension: {String}, ("both")
+ horizontalAlign: {String}, ("center")
+ verticalAlign: {String}, ("center")
+ textAlign: {String}, ("center")
+ whiteSpace: {String}, ("nowrap")
});
@@ -28,6 +28,8 @@ fontSizeFactor: This option is used to give some vertical spacing for letters th
maximumFontSize: maximum font size to use.
+minimumFontSize: minimum font size to use. if font is calculated smaller than this, text will be rendered at this size and wrapped
+
limitingDimension: In which dimension the font size should be limited. Possible values: "width", "height" or "both". Defaults to both. Using this option with values different than "both" overwrites the element parent width or height.
horizontalAlign: Where to align the text horizontally. Possible values: "left", "center", "right". Defaults to "center".
@@ -154,6 +156,7 @@ export default function BigText(element, options) {
width: element.offsetWidth, //Note: This is slightly larger than the jQuery version
height: element.offsetHeight,
};
+ if (!box.width || !box.height) return element;
if (options.rotateText !== null) {
@@ -187,7 +190,20 @@ export default function BigText(element, options) {
lineHeight = Math.floor(heightFactor * 1000);
var fontSize = lineHeight * options.fontSizeFactor;
- if (options.maximumFontSize !== null && fontSize > options.maximumFontSize) {
+ if (fontSize < options.minimumFontSize) {
+ parentStyle.display = "flex";
+ parentStyle.alignItems = "center";
+ style.whiteSpace = "pre-wrap";
+ style.textAlign = "center";
+ style.visibility = "";
+ style.fontSize = "18px";
+ style.lineHeight = "20px";
+ style.top = "";
+ style.left = "";
+ style.margin = "";
+ return element;
+ }
+ if (options.maximumFontSize && fontSize > options.maximumFontSize) {
fontSize = options.maximumFontSize;
lineHeight = fontSize / options.fontSizeFactor;
}
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index 6a0d651d2..42e158584 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -4,7 +4,7 @@
border-radius: inherit;
display: flex;
flex-direction: column;
- position: absolute;
+ position: relative;
}
.labelBox-mainButton {
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 0015f0b71..d539ca9b8 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
-import { Cast, StrCast } from '../../../fields/Types';
+import { Cast, StrCast, NumCast } from '../../../fields/Types';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -73,6 +73,19 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
@observable _mouseOver = false;
@computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; }
+ fitTextToBox = (r: any) => {
+ BigText(r, {
+ rotateText: null,
+ fontSizeFactor: 1,
+ minimumFontSize: NumCast(this.layoutDoc._minFontSize),
+ maximumFontSize: NumCast(this.layoutDoc._maxFontSize),
+ limitingDimension: "both",
+ horizontalAlign: "center",
+ verticalAlign: "center",
+ textAlign: "center",
+ whiteSpace: "nowrap"
+ });
+ }
// (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
render() {
const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
@@ -97,16 +110,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
}} >
<span ref={r => {
if (r) {
- BigText(r, {
- rotateText: null,
- fontSizeFactor: 1,
- maximumFontSize: null,
- limitingDimension: "both",
- horizontalAlign: "center",
- verticalAlign: "center",
- textAlign: "center",
- whiteSpace: "nowrap"
- });
+ if (!r.offsetWidth || !r.offsetHeight) setTimeout(() => this.fitTextToBox(r));
+ else this.fitTextToBox(r);
}
}}>{label.startsWith("#") ? (null) : label}</span>
</div>
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index d5f3f3b4e..55f6d6059 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -15,6 +15,7 @@ import { DocumentView, DocumentViewSharedProps } from "./DocumentView";
import './LinkDocPreview.scss';
import React = require("react");
import { DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
interface LinkDocPreviewProps {
linkDoc?: Doc;
@@ -137,7 +138,13 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
@computed get previewHeader() {
return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
<div className="linkDocPreview-info" ref={this._infoRef}>
- <div className="linkDocPreview-title">
+ <div className="linkDocPreview-title" style={{ pointerEvents: "all" }}
+ onPointerDown={e => {
+ DragManager.StartDocumentDrag([this._infoRef.current!],
+ new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY);
+ e.stopPropagation();
+ LinkDocPreview.Clear();
+ }}>
{StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}
<p className="linkDocPreview-description"> {StrCast(this._linkDoc.description)}</p>
</div>
@@ -188,6 +195,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
ContainingCollectionDoc={undefined}
ContainingCollectionView={undefined}
renderDepth={-1}
+ suppressSetHeight={true}
PanelWidth={this.width}
PanelHeight={this.height}
focus={DocUtils.DefaultFocus}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a2e7d2aa3..23749d479 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -2,10 +2,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
+import { CreateImage } from "../nodes/WebBoxRenderer";
import "pdfjs-dist/web/pdf_viewer.css";
-import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types';
-import { PdfField } from "../../../fields/URLField";
+import { ImageField, PdfField } from "../../../fields/URLField";
import { TraceMobx } from '../../../fields/util';
import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs } from '../../documents/Documents';
@@ -21,6 +22,8 @@ import { SidebarAnnos } from '../SidebarAnnos';
import { FieldView, FieldViewProps } from './FieldView';
import "./PDFBox.scss";
import React = require("react");
+import { Id } from '../../../fields/FieldSymbols';
+import { VideoBox } from './VideoBox';
@observer
export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
@@ -52,6 +55,56 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
+ replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => {
+ if (oldDiv.childNodes) {
+ for (let i = 0; i < oldDiv.childNodes.length; i++) {
+ this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement);
+ }
+ }
+ if (oldDiv instanceof HTMLCanvasElement) {
+ const canvas = oldDiv;
+ const img = document.createElement('img'); // create a Image Element
+ img.src = canvas.toDataURL(); //image source
+ img.style.width = canvas.style.width;
+ img.style.height = canvas.style.height;
+ const newCan = newDiv as HTMLCanvasElement;
+ const parEle = newCan.parentElement as HTMLElement;
+ parEle.removeChild(newCan);
+ parEle.appendChild(img);
+ }
+ }
+
+ updateIcon = () => {
+ const docViewContent = this.props.docViewPath().lastElement().ContentDiv!;
+ const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
+ newDiv.style.width = (this.layoutDoc[WidthSym]()).toString();
+ newDiv.style.height = (this.layoutDoc[HeightSym]()).toString();
+ this.replaceCanvases(docViewContent, newDiv);
+ const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv);
+ const nativeWidth = this.layoutDoc[WidthSym]();
+ const nativeHeight = this.layoutDoc[HeightSym]();
+
+ CreateImage(
+ "",
+ document.styleSheets,
+ htmlString?.replace(/docView-hack/g, 'documentView-hack'),
+ nativeWidth,
+ nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(),
+ NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"])
+ ).then
+ ((data_url: any) => {
+ VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
+ returnedfilename => setTimeout(action(() => {
+ this.dataDoc.icon = new ImageField(returnedfilename);
+ this.dataDoc["icon-nativeWidth"] = nativeWidth;
+ this.dataDoc["icon-nativeHeight"] = nativeHeight;
+ }), 500));
+ })
+ .catch(function (error: any) {
+ console.error('oops, something went wrong!', error);
+ });
+ }
+
componentWillUnmount() { this._selectReactionDisposer?.(); }
componentDidMount() {
this.props.setContentView?.(this);
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 3cf10a033..f47a71469 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -81,6 +81,7 @@
align-items: center;
justify-content: center;
display: flex;
+ width: 100%;
visibility: none;
opacity: 0;
background-color: $dark-gray;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index ca8dc8515..e57cb1abe 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,10 +4,10 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio
import { observer } from "mobx-react";
import { basename } from "path";
import * as rp from 'request-promise';
-import { Doc, DocListCast } from "../../../fields/Doc";
+import { Doc, DocListCast, HeightSym, WidthSym } from "../../../fields/Doc";
import { InkTool } from "../../../fields/InkField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { AudioField, VideoField } from "../../../fields/URLField";
+import { AudioField, ImageField, VideoField } from "../../../fields/URLField";
import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
@@ -53,14 +53,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
* @param returnedFilename the base filename to store the image on the server
* @param nosuffix optionally suppress creating multiple resolution images
*/
- public static async convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false) {
+ public static async convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename?: string) {
try {
const posting = Utils.prepend("/uploadURI");
const returnedUri = await rp.post(posting, {
body: {
uri: imageUri,
name: returnedFilename,
- nosuffix
+ nosuffix,
+ replaceRootFilename
},
json: true,
});
@@ -220,16 +221,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// creates and links snapshot photo of current video frame
- @action public Snapshot(downX?: number, downY?: number) {
+ @action public Snapshot = (downX?: number, downY?: number, cb?: (filename: string, x: number | undefined, y: number | undefined) => void) => {
const width = NumCast(this.layoutDoc._width);
const canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = 640 * Doc.NativeHeight(this.layoutDoc) / (Doc.NativeWidth(this.layoutDoc) || 1);
const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
if (ctx) {
- // ctx.rect(0, 0, canvas.width, canvas.height);
- // ctx.fillStyle = "blue";
- // ctx.fill();
this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
}
@@ -259,10 +257,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
const encodedFilename = encodeURIComponent("snapshot" + retitled + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_"));
const filename = basename(encodedFilename);
VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) =>
- returnedFilename && this.createRealSummaryLink(returnedFilename, downX, downY));
+ returnedFilename && (cb ?? this.createRealSummaryLink)(returnedFilename, downX, downY));
}
}
+ updateIcon = () => {
+ const makeIcon = (returnedfilename: string) => {
+ this.dataDoc.icon = new ImageField(returnedfilename);
+ this.dataDoc["icon-nativeWidth"] = this.layoutDoc[WidthSym]();
+ this.dataDoc["icon-nativeHeight"] = this.layoutDoc[HeightSym]();
+ };
+ this.Snapshot(undefined, undefined, makeIcon);
+ }
+
// creates link for snapshot
createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => {
const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c12059943..6a3c6336d 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -130,8 +130,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.setScrollPos(this._initialScroll);
}
}));
- } else if (!this.props.isContentActive() &&
- !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && /// don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
+ } else if ((!this.props.isContentActive() || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail)
+ !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty
LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href;
if (this._iframe && !imageBitmap) {
@@ -150,7 +150,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
NumCast(this.layoutDoc._scrollTop)
).then
((data_url: any) => {
- VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-thumb" + (new Date()).getTime(), true).then(
+ VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then(
returnedfilename => setTimeout(action(() => this.layoutDoc.thumb = new ImageField(returnedfilename)), 500));
})
.catch(function (error: any) {
@@ -164,7 +164,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
autoHeight => {
if (autoHeight) {
this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
- this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+ this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
}
});
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ec88b8d1d..710270be4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,23 +1,23 @@
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEqual } from "lodash";
-import { action, computed, IReactionDisposer, reaction, runInAction, observable, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap, selectAll } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { inputRules } from 'prosemirror-inputrules';
import { keymap } from "prosemirror-keymap";
import { Fragment, Mark, Node, Slice } from "prosemirror-model";
-import { ReplaceStep } from 'prosemirror-transform';
import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../../fields/DateField';
-import { AclAdmin, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym, AclAugment } from "../../../../fields/Doc";
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, DocListCast, DocListCastAsync, Field, ForceServerWrite, HeightSym, Opt, UpdatingFromServer, WidthSym } from "../../../../fields/Doc";
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { PrefetchProxy } from '../../../../fields/Proxy';
import { RichTextField } from "../../../../fields/RichTextField";
import { RichTextUtils } from '../../../../fields/RichTextUtils';
-import { Cast, DateCast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
@@ -29,6 +29,7 @@ import { DictationManager } from '../../../util/DictationManager';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from "../../../util/DragManager";
import { makeTemplate } from '../../../util/DropConverter';
+import { LinkManager } from '../../../util/LinkManager';
import { SelectionManager } from "../../../util/SelectionManager";
import { SnappingManager } from '../../../util/SnappingManager';
import { undoBatch, UndoManager } from "../../../util/UndoManager";
@@ -38,10 +39,11 @@ import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
import { ViewBoxAnnotatableComponent } from "../../DocComponent";
import { DocumentButtonBar } from '../../DocumentButtonBar';
+import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
+import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
-import { AudioBox } from '../AudioBox';
import { FieldView, FieldViewProps } from "../FieldView";
import { LinkDocPreview } from '../LinkDocPreview';
import { DashDocCommentView } from "./DashDocCommentView";
@@ -60,10 +62,6 @@ import { schema } from "./schema_rts";
import { SummaryView } from "./SummaryView";
import applyDevTools = require("prosemirror-dev-tools");
import React = require("react");
-import { SidebarAnnos } from '../../SidebarAnnos';
-import { Colors } from '../../global/globalEnums';
-import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { LinkManager } from '../../../util/LinkManager';
const translateGoogleApi = require("translate-google-api");
export interface FormattedTextBoxProps {
@@ -364,7 +362,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
updateTitle = () => {
- const title = StrCast(this.dataDoc.title)
+ const title = StrCast(this.dataDoc.title);
if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
(title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] &&
(Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) {
@@ -758,7 +756,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
let tr = state.tr.addMark(sel.from, sel.to, splitter);
if (sel.from !== sel.to) {
- const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true });
+ const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true });
const href = targetHref ?? Doc.localServerPath(anchor);
if (anchor !== anchorDoc) this.addDocument(anchor);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
@@ -859,7 +857,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight));
}, { fireImmediately: true });
- this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
+ this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l));
this._cachedLinks = newLinks;
@@ -1526,7 +1524,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (children) {
const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins);
const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight);
- if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
+ if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight;
if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) {
setScrollHeight();
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 0a91bf2ab..4fa52565e 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -98,7 +98,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable private openMovementDropdown: boolean = false;
@observable private openEffectDropdown: boolean = false;
@observable private presentTools: boolean = false;
- @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); }
+ @computed get childDocs() { return DocListCast(this.rootDoc[this.fieldKey]); }
@computed get tagDocs() {
const tagDocs: Doc[] = [];
for (const doc of this.childDocs) {
@@ -124,7 +124,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this);
if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations.
Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
- title: "pres element template", type: DocumentType.PRESELEMENT, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
+ title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
}));
// this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement
// this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent
@@ -310,7 +310,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (!group) this._selectedArray.clear();
this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array
- if (this.layoutDoc._viewType === "stacking" && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
+ if ([CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.layoutDoc._viewType as any) && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
this.onHideDocument(); //Handles hide after/before
}
});
@@ -627,9 +627,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
//@ts-ignore
const viewType = e.target.selectedOptions[0].value as CollectionViewType;
// pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
- viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined);
+ [CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined);
this.rootDoc._viewType = viewType;
- if (viewType === CollectionViewType.Stacking) this.layoutDoc._gridGap = 0;
+ if ([CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType)) this.layoutDoc._gridGap = 0;
});
/**
@@ -703,8 +703,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
return true;
}
- childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement;
- removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc);
+ childLayoutTemplate = () => ![CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.rootDoc._viewType as any) ? undefined : this.presElement;
+ removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
panelHeight = () => this.props.PanelHeight() - 40;
isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && this.props.layerProvider?.(this.layoutDoc) !== false) &&
@@ -2264,13 +2264,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const isMini: boolean = this.toolbarWidth <= 100;
return (
<div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? "none" : undefined }}>
- {isMini || Doc.UserDoc().noviceMode ? (null) : <select className="presBox-viewPicker"
+ {isMini ? (null) : <select className="presBox-viewPicker"
style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }}
onPointerDown={e => e.stopPropagation()}
onChange={this.viewChanged}
value={mode}>
<option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}>List</option>
- <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>
+ <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}>Tree</option>
+ {Doc.UserDoc().noviceMode ? (null) : <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}>3D Carousel</option>}
</select>}
<div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}>
<span className={`presBox-button ${this.layoutDoc.presStatus === "edit" ? "present" : ""}`}>
@@ -2481,7 +2482,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
ScriptingGlobals.add(function lookupPresBoxField(container: Doc, field: string, data: Doc) {
if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data);
- if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 35 : 31;
+ if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(container._viewType as any) ? 35 : 31;
if (field === 'presStatus') return container.presStatus;
if (field === '_itemIndex') return container._itemIndex;
if (field === 'presBox') return container;
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index dd0520bad..789a6d9f3 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -42,7 +42,7 @@ $slide-active: #5B9FDD;
background-color: #d5dce2;
border-radius: 5px;
height: calc(100% - 7px);
- width: calc(100% - 15px);
+ width: 100%;
display: grid;
grid-template-rows: 16px 10px auto;
grid-template-columns: max-content max-content max-content max-content auto;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 326c6bd8e..8486dbc0f 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -23,6 +23,7 @@ import { PresBox } from "./PresBox";
import "./PresElementBox.scss";
import { PresMovement } from "./PresEnums";
import React = require("react");
+import { CollectionViewType } from "../../collections/CollectionView";
/**
* This class models the view a document added to presentation will have in the presentation.
* It involves some functionality for its buttons and options.
@@ -87,6 +88,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
isContentActive={this.props.isContentActive}
addDocTab={returnFalse}
pinToPres={returnFalse}
+ fitContentsToDoc={returnTrue}
PanelWidth={this.embedWidth}
PanelHeight={this.embedHeight}
ScreenToLocalTransform={Transform.Identity}
@@ -190,6 +192,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem = this.rootDoc;
const dragArray = PresBox.Instance._dragArray;
const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
+ if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
+ dragData.dropAction = "move";
+ dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
+ dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument;
const dragItem: HTMLElement[] = [];
if (dragArray.length === 1) {
const doc = dragArray[0];
@@ -421,7 +427,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
}}>
- <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+ <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - (this.presBox._viewType === CollectionViewType.Stacking ? 195 : 220)) : toolbarWidth - (this.presBox._viewType === CollectionViewType.Stacking ? 105 : 145), cursor: isSelected ? 'text' : 'grab' }}>
<EditableView
ref={this._titleRef}
editing={!isSelected ? false : undefined}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 651b0401b..92a69c02b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -74,7 +74,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _selectionText: string = "";
private _downX: number = 0;
private _downY: number = 0;
@@ -123,7 +123,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
autoHeight => {
if (autoHeight) {
this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]);
- this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
+ this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1));
}
});
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index d5254e315..0af7de6af 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -7,7 +7,7 @@ import { StrCast } from '../../../fields/Types';
import { Utils } from '../../../Utils';
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { SettingsManager } from "../../util/SettingsManager";
-import { undoBatch } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import { Borders, Colors } from "../global/globalEnums";
import "./TopBar.scss";
@@ -33,30 +33,36 @@ export class TopBar extends React.Component {
</div>
<div className="topbar-center" >
<div className="topbar-lozenge-dashboard">
- <select className="topbar-dashSelect" onChange={e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)])}
+ <select className="topbar-dashSelect" onChange={undoBatch(e => CurrentUserUtils.openDashboard(Doc.UserDoc(), myDashboards[Number(e.target.value)]))}
value={myDashboards.indexOf(CurrentUserUtils.ActiveDashboard)}
style={{ color: Colors.WHITE }}>
{myDashboards.map((dash, i) => <option key={dash[Id]} value={i}> {StrCast(dash.title)} </option>)}
</select>
</div>
<div className="topbar-dashboards">
- <div className="topbar-icon" onClick={undoBatch(() => CurrentUserUtils.createNewDashboard(Doc.UserDoc()))}
- >
- {"New"}<FontAwesomeIcon icon="plus"></FontAwesomeIcon>
+ <div className="topbar-icon" onClick={async () => {
+ const batch = UndoManager.StartBatch("new dash");
+ await CurrentUserUtils.createNewDashboard(Doc.UserDoc());
+ batch.end();
+ }}>
+ {"New"}<FontAwesomeIcon icon="plus" />
</div>
- {Doc.UserDoc().noviceMode ? (null) : <div className="topbar-icon" onClick={undoBatch(() => CurrentUserUtils.snapshotDashboard(Doc.UserDoc()))}
- >
- {"Snapshot"}<FontAwesomeIcon icon="camera"></FontAwesomeIcon>
+ {<div className="topbar-icon" onClick={async () => {
+ const batch = UndoManager.StartBatch("snapshot");
+ await CurrentUserUtils.snapshotDashboard(Doc.UserDoc());
+ batch.end();
+ }}>
+ {"Snapshot"}<FontAwesomeIcon icon="camera" />
</div>}
</div>
</div>
<div className="topbar-right" >
<div className="topbar-icon" onClick={() => window.open(
"https://brown-dash.github.io/Dash-Documentation/", "_blank")}>
- {"Help"}<FontAwesomeIcon icon="question-circle"></FontAwesomeIcon>
+ {"Help"}<FontAwesomeIcon icon="question-circle" />
</div>
<div className="topbar-icon" onClick={() => SettingsManager.Instance.open()}>
- {"Settings"}<FontAwesomeIcon icon="cog"></FontAwesomeIcon>
+ {"Settings"}<FontAwesomeIcon icon="cog" />
</div>
</div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ebdbae344..50e5fcbc4 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -11,7 +11,7 @@ import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGloba
import { SelectionManager } from "../client/util/SelectionManager";
import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
import { UndoManager } from "../client/util/UndoManager";
-import { DashColor, intersectRect, Utils } from "../Utils";
+import { DashColor, incrementTitleCopy, intersectRect, Utils } from "../Utils";
import { DateField } from "./DateField";
import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols";
import { List } from "./List";
@@ -770,7 +770,7 @@ export namespace Doc {
return overwrite;
}
- export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc {
+ export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc {
const copy = new Doc(copyProtoId, true);
const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []);
Object.keys(doc).forEach(key => {
@@ -806,6 +806,9 @@ export namespace Doc {
}
copy.context = undefined;
Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared");
+ if (retitle) {
+ copy.title = incrementTitleCopy(StrCast(copy.title));
+ }
return copy;
}
@@ -1108,7 +1111,7 @@ export namespace Doc {
if (typeof value === "string") {
value = value.replace(`,${Utils.noRecursionHack}`, "");
}
- const fieldVal = doc[key];
+ const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key];
if (Cast(fieldVal, listSpec("string"), []).length) {
const vals = Cast(fieldVal, listSpec("string"), []);
const docs = vals.some(v => (v as any) instanceof Doc);
@@ -1119,7 +1122,7 @@ export namespace Doc {
return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
- export function deiconifyView(doc: any) {
+ export function deiconifyView(doc: Doc) {
StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index bfa07d47a..5e0cd67cc 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -3,7 +3,7 @@ import { Method, _success } from "../RouteManager";
import * as formidable from 'formidable';
import v4 = require('uuid/v4');
const AdmZip = require('adm-zip');
-import { extname, basename, dirname } from 'path';
+import { extname, basename, dirname, } from 'path';
import { createReadStream, createWriteStream, unlink, writeFile } from "fs";
import { publicDirectory, filesDirectory } from "..";
import { Database } from "../database";
@@ -13,10 +13,8 @@ import { AcceptableMedia, Upload } from "../SharedMediaTypes";
import { normalize } from "path";
import RouteSubscriber from "../RouteSubscriber";
const imageDataUri = require('image-data-uri');
-import { isWebUri } from "valid-url";
-import { Opt } from "../../fields/Doc";
import { SolrManager } from "./SearchManager";
-import { StringDecoder } from "string_decoder";
+let fs = require('fs');
export enum Directory {
parsed_files = "parsed_files",
@@ -260,10 +258,16 @@ export default class UploadManager extends ApiManager {
const uri = req.body.uri;
const filename = req.body.name;
const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original;
+ const deleteFiles = req.body.replaceRootFilename;
if (!uri || !filename) {
res.status(401).send("incorrect parameters specified");
return;
}
+ if (deleteFiles) {
+ const path = serverPathToFile(Directory.images, "");
+ let regex = new RegExp(`${deleteFiles}.*`);
+ fs.readdirSync(path).filter((f: any) => regex.test(f)).map((f: any) => fs.unlinkSync(path + f));
+ }
return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
const ext = extname(savedName).toLowerCase();
const { pngs, jpgs } = AcceptableMedia;